22.12.2002: ну вроде как готово. Надо теперь сбагрить это Олегу и убедиться, что оно действительно функционирует безглючно.
19.12.2002: готово. Там используется виджет Gridbox версии 2.0 от Ed Falk (http://www.efalk.org/). Версия 2.0 потребовалась потому, что она поддерживает флажок -DUSE_MOTIF, при котором наследование идет не напрямую от Constraint (при этом FocusPolicy -- FocusFollowsMouse), а от XmManager, т.е. виджет имеет всю motif'овскую функциональность.
Но при этом мы теряем "сетку" из сепараторов, так что лучше переходить на виджет от AAntonov'а, в котором есть родные сепараторы.
19.12.2002: у Gridbox есть constraint-ресурс XtNgravity, принимающий значения NorthWestGravity,NorthGravity,..., работающий в паре с constraint-ресурсом XtNfill, который надлежит выставлять в FillNone, дабы он не подавлял gravity. Для collabels gravity ставится в SouthGravity (дабы работали многострочные метки с выравниванием по нижнему краю), у rowlabels и каналов -- NorthWestGravity.
15.01.2003: нашел еще один виджет -- Table из комплекта WINTERP (http://www.cybertribe.com/mayer/winterp/). У него есть плюс перед Gridbox: поддерживаются отдельные ресурсы XmNverticalSpacing и XmNhorizontalSpacing. Зато позиция указывается хитрее -- не нормальным ресурсами, а хрен-знает-как-сложной строкой XtNlayout. Зато можно использовать и "позиционирующую процедуру" XtTblPosition. В общем, надо с ним поразбираться. И, возможно, изготовить унифицированную, виджетонезависимую библиотеку с функциями "создать виджет-сетку" и "назначить виджету координаты и alignment".
17.01.2003: библиотека изготовлена, и теперь
oldChl_data.c пользуется именно ей. Это компонент Xh'а,
функции XhGrid*()
. Осталось вставить инфраструктуру
для поддержки нескольких разных виджетов-сеток, с переключением из
Makefile'а.
18.01.2003: все готово. Используемый виджет
переключается директивой
CPPFLAGS+=-DUSE_WIDGET={GRIDBOX|TABLE|SGL}
в
lib/Xh/Makefile (правда, SGL пока не работает).
Сам виджет Table -- кривой: там параметры выравнивание, заполнение и
еще кой-какие выставляются в одном слове Options, но читать это слово
нельзя -- нету такой функции. Так что пришлось в принудительном
порядке из XhGridSetChildAlignment()
отключать filling, а
XhGridSetChildFilling не делает ничего. Плюс у него начинаются глюки
при установке distances != 1.
??.02.2003: SGL реально поддерживается. Вот только глючит он пока. Саша Антонов с ним разбирается.
26.03.2003: Ура! SGL назначен основным менеджером-сеткой, глюки вроде как исправлены. Оставшаяся пока неработающая фича -- выравнивание виджетов внутри клетки, но это уж совсем не смертельно, и Саша Антонов это вскорости сделает.
28.03.2003: выравнивание сделано, работает.
Идеология -- как в Gridbox, т.е. один ресурс
XmNcellGravity
. (Был, правда, еще один глюк: у Саши
по-прежнему приколы с интерпретацией того, что же такое margin, и он
выравнивал виджет не внутри прямоугольника, учитывающего
margin'ы, а внутри прямоугольника, содержащего их.)
XmSepGridLayout не поддерживает концепцию "filling" виджета внутри
клетки, поэтому XhGridSetChildFilling не делает ничего -- но это просто
и не нужно, поскольку oldChl_data.c::ChlCreateElement()
и
так в принудительном порядке этот "filling" отключает.
Хотя, возможно, еще один глюк все же
есть: по экспериментам Андрея Чугуева,
XmNcellGravity
игнорируется для виджета в позиции (0,0) --
или для самого первого виджета?
14.05.2003: (реально -- фиг знает сколько месяцев назад) расследование показало, что возможности выравнивания по baseline -- нету, точка.
(XmRowColumn умеет это делать, но только для тех children, которые subclass'ы XmLabel и XmText. Конечно, можно было бы попробовать разобраться, но, боюсь, это будет глючновато, непортабельно, и едва ли стоит времени.)
14.05.2003: опрос свидетелей (А.Антонова) показал, что глюк с игнорированием gravity в (0,0) исправлен, причем сравнительно давно (месяц?). Посему наконец-то помечаем как "done".
11.06.2003: найдена еще одна "баговатость" в SGL: какое-то бо-бо в SINGLECOL-элементах. Расследование показало, что, по крайней мере, частично, проблема в том, что при создании сетки размеры ей не ставятся, т.е., работает режим авторастяжения; а при создании виджетов им координаты в сетке тоже не ставятся -- назначаются потом. Из-за этого все весьма хорошо едет.
Решение: 1) в Xh_grid.c::XhCreateGrid()
при создании SGL ему ставится XmNresFlag=False
; при этом,
впрочем, сетка оказывается размером 1x1, посему 2) сделана функция
XhGridSetSize(w,cols,rows)
, которая вызывается из
ChlCreateElement()
-- отдельно для SINGLECOL/MULTICOL.
Что интересно, после переключения на FontStruct исчезла проблема с пустыми строками на кнопках toolbar'а.
Осталось разобраться с двумя вещами: 1) он не делает никаких полей вокруг текста; 2) узнать, как сделать второй виджет, для "больших значений", а главное -- динамически изменяемый.
13.05.2003: поскольку фичу "увеличенный вывод значения" решено сделать окошком, а не tooltip'ом, то считаем, что на текущий момент tooltips в Chl/Xh реализованы (если будет время -- разберусь с margins вокруг лэйбла в LiteClue) и помечаем раздел как done.
11.06.2003: баг -- не баг, но неудобство: LiteClue поддерживает только однострочные типы. Так уж он устроен -- увы.
30.09.2004: пытался поручить вопрос многострочности и полей Антонову -- без толку. Зато, когда разбирался с установкой размеров LOGD_ARROW_* (см. bigfile-0001.html), порядком образовался на тему "XmString: с чем его едят и как его рисуют". Посему -- краткий план действий:
char *text
сохраняем
XmString
, полученный из него при помощи
XmStringCreateLtoR(text,XmSTRING_DEFAULT_CHARSET)
.
XmStringExtent()
, определяем его
размеры, и рисуем его через XmStringDraw()
.
XtGetSubresources
.
27.07.2005: занялся сим делом -- ну и бе-е-е! Там столько возни... Так что забил и перетащил то, что успел сделать, в ~/work/XmLiteClue/.
13.02.2007: вторая попытка -- в основном сделал сходу, буквально за пару часов. И где я там полтора года назад увидел "столько возни"?... Осталось только взятие полей из ресурсов сделать.
13.02.2007: да, и ресурсы
marginWidth,marginHeight добавил (по умолчанию -- 2,2). (И нафига я
тогда хотел через XtGetSubresources
, а не напрямую? :-)
И убрал из XmLiteClueAddWidget()
нафиг не нужный
параметр size
(ибо у нас всегда NUL-terminated) и никогда
не использовавшийся option
.
И еще вчера Xh_balloon.c переведен на XmLiteClue. Замечательно -- теперь у нас есть многострочные тултипы!!!
07.02.2008: с изначальных времен имелась сильная
неправильность -- использовалась статическая переменная
Xh_balloon.c::clue
, что было бы совершенно
некорректно для многооконных программ (каковые мы, к моему стыду, за
10 лет так и не использовали, кроме той древней программы управления
запяткинским клистроном).
Поэтому теперь сделано и используется поле
_WindowInfo.clue
.
Из этого вылезло следствие: для доступа к надлежащему экземпляру
clue надо делать XhWindowOf(w)
, которая пользуется списком
окон; а поскольку с изначальных же времен добавление к списку делалось
уже в конце XhCreateWindow()
, то прилепление тултипов
кнопкам тулбара обламывается. Поэтому -- вызов
addToWindowList()
в XhCreateWindow()
теперь
перенесен вверх, сразу после минимальной инициализации структур окна.
В 4cx/ этот фикс также добавлен.
26.05.2010: Роговским было обнаружено, что при некоторых условиях тултипы начинают брендить -- что-то как-то накладывается.
Происходит это с CHOICEBS, причём, похоже, в случае, когда тултипы есть и у ручки вообще, и у конкретной кнопочки. Такое ощущение, что пишется ДВЕ строчки поверх друг дружки; причём -- на мгновение промелькивает тултипчик где-то чуть сбоку.
20.12.2002: имеется поле {logchannet,logchanbin}.widdepinfo, selector уже переведен на него.
15.05.2003: за прошедшие полгода отдельный API для парсинга так и не понадобился -- так что забиваем на это требование и помечаем раздел как "done".
13.02.2007: замечание ну так, ну очень задним числом: еще весной 2004г. был сготовлен paramstr_parser, практически ровно под эти потребности, и им пользуются для парсинга widdepinfo все типы ручек, КРОМЕ селектора :-).
??.04.2003: сделал
_data.c::ParseDoubleFormat()
, раздербанивающую формат на
части.
09.06.2003: перенес
ParseDoubleFormat()
в _internals.c, сделал там же
GetTextColumns(dpyfmt,units)
, вычисляющую потребное для
отображения количество символов, и GetTextFormat()
,
реализующую всегда-добывание строки формата -- в т.ч. default и из
стандартных форматов.
Кстати: понятие "стандартных форматов" слегка поменялось -- это теперь pointer-константы с целочисленными значениями [1..999], являющиеся номером стандартного формата. Вопрос: может, все-таки, для вящей портабельности, вернуться к нормальной строковой схеме? А если захочется -- то ничто не мешает строки загнать в #define'ы.
Сменил logchanbin_t.dpyfmt
с char*
на
char[16]
, чтобы туда попадала уже готовая,
отфильтрованная/проверенная строка формата.
Вставил в _data.c вычитывание info->dpyfmt
и заполнение bi->dpyfmt
. В _text_widget.c
теперь указывается ресурс XmNcolumns
исходя из числа,
возвращаемого GetTextColumns()
.
Итого -- оно по минимуму работает.
10.06.2003: важную вещь вчера забыл: избавиться
от DBLFORMAT
и FULLFORMAT
-- при собственно
sprintf'е использовались они.
Исправил -- закомментировал их в _text_widget.c нафиг, и
вставил использование dpyfmt
. То же самое проделал с
_dial_widget.c (эх, ну давно ведь хотел унифицировать код
работы с текстовыми полями -- ан нет, опять пришлось тупо
копировать...). Также теперь оно используется в _data.c
при сохранении файла режима. Полностью избавляться пока не стал из-за
ChlPrintWindow()
, который менять лень, а выкинуть --
жалко. Общее замечание/проблема: при использовании dpyfmt
приходится разбивать один printf на несколько, т.к. слить-то переменные
форматы, в отличие от строковых констант, в одну строку -- низзя.
Кроме того, слегка переделана логика GetTextColumns()
:
теперь она возвращает точную ширину, без учета места под курсор,
а кому надо -- добавляют 1 сами.
После обеда: переделал понятие "стандартных форматов" в
GetTextFormat()
так, как предполагалось первоначальным
проектом -- "$formatname".
Сделал в GetTextColumns()
более-менее приличные
значения ширины по умолчанию (если не указано width и/или precision).
10.06.2003: итак, вроде работает. На сейчас помечаем как "done". Далее надо будет еще потестировать работу этой фичи, плюс набить список стандартных форматов. Плюс еще -- "локальные" форматы, по некоторому размышлению, сочтены излишними.
23.12.2002: сделал, отображается. Только теперь надо найти все-таки нормальный tooltip-widget, который не брезгует отображать tooltip на XmLabel, а то у Motif-2.2'вского с этим проблемы ;-).
(записано 15.05.2003): да найден давно нормальный tooltip-widget :-).
11.06.2003: слегка поменял методику: теперь при непустом поле "comment" tooltip отображается на самом виджете. Заголовки -- collabels и rowlabels -- не трогаются; если захочется -- сделаем как в следующем пункте.
16.09.2004: записываю только сейчас, но сделано
давным-давно (судя по BACKUP/+STABLE/, константа
MULTISTRING_OPTION_SEPARATOR
появилась между
STABLE/cx.20030828_Knobs и
BACKUP/cx_src.20031017_proto_and_errstats).
Идея: введена MULTISTRING_OPTION_SEPARATOR='\f'
(Control+L), которая если встречается в лэйбле, то разделяет собственно
текст метки и текст тултипа. А вся эта логика сидит (сейчас :-) в
Chl_gui.c::get_label_and_tip()
.
Соответственно, никакие поля к описанию элемента добавлять не понадобилось.
27.12.2002: Сделано для curcx, теперь надо портировать в cx. Это простейшая штука: XmText, отображающий bi->label, а при отсутствии оного -- строку из 3 пробелов; с ресурсами, идентичными оным у value. Единственный фокус -- для пущего эффекта оно делается выпуклым путем перестановки XmNtopShadowColor и XmNbottomShadowColor сразу после XtVaCreateManagedWidget.
20.01.2003: портировано в CX. Работает.
15.03.2003: в первом приближении готово (хвала Джону Брэдли и его XV), он уже вполне usable. Что еще надо сделать:
Необходимое:
21.03.2003: автоповтор сделан, по образу и подобию
оного из IncDecB.c. Теперь вместо XmNactivateCallback я ловлю
XmNarmCallback. Для унификации модификаторов Control/Alt/Shift из
_text_widget.c всякие ControlMask вынесены в
_internals.c и названы там
MOD_{FINEGRAIN,BIGSTEPS,NOSEND}_MASK
, а
_{text,dial}_widget.c пользуются ими. Автоматически при этом
в LOGD_DIAL модификаторы работают и с клавиатуры.
17.03.2003: сделана обработка мыши, числа типа
-60/+240 уже вынесены в enum
.
15.09.2004: давным-давно готово, а как "done" помечаю только сегодня...
SetControlValue()
стоит проверка
if(!bi->is_rw)return;
).
24.03.2003: сделано очень просто -- всей троице
(dial_w, {dec,inc}bt_w) ставится ресурс
XmNsensitive:=bi->is_rw
. Надо будет не забыть
аналогичную вещь в реализации появления поля ввода.
15.09.2004: реализовано поле ввода, без алхимии с появлением по щелчку, детали см. ниже за это же число.
Wishlist:
17.03.2003: сделано.
15.09.2004: сделано, детали см. ниже за это же число.
05.06.2003: нарыл в motif/lib/Xm/ место,
где в XmText есть код, похожий на требуемый нам --
TextOut.c::HandleFocusEvents()
. Что интересно,
какое-то подобие было в Text.c::LosingFocus()
, но
эта функция нигде не вызывается.
10.06.2003: сделал "по образу и подобию"
установку обработчика
FocusChangeMask|
на dial_w.
В обработчике фокус ловится. Единственное что -- поскольку я
реально проверяю только Focus{In,Out}
, а
{Enter,Leave}Notify
пока игнорирую, то с
focus_policy=POINTER оно пока ничего не ловит.
Добавил в DialDraw()
доп. параметр --
int highlight
-- рисовать ли прямоугольник
"фокусированности", и вызываю с highlight=yes из
ExposeCallback()
и из FocusHandler()
.
Основная кривость -- пришлось скопировать из
motif/lib/Xm/Primitive.c::UnhighlightBorder()
вычитывание GC parent'а, а для этого пришлось сделать #include файлов
PrimitiveP.h и ManagerP.h, что не есть good. Плюс, в
качестве цвета для прямоугольника "фокусированности" используется
dial_gc
, что также есть криво (но что поделаешь -- нету у
XmDrawingArea, поскольку оно -- Manager, такого понятия, как
highlight{Color,Pixmap,Thickness}...).
Плюс, теперь есть enum HL_THICKNESS
, участвующий в
арифметике вычисления геометрии индикатора -- сколько пикселов по бокам
оставлять для прямоугольника "фокусированности". В принципе, можно
использовать это поле, если захочется вместо
XDrawRectangle()
вызывать XmeDrawHighlight()
(для чего потребуется так же сделать #include DrawP.h).
11.06.2003: сделал рисование прямоугольника при
помощи XmeDrawHighlight()
. Теперь
HL_THICKNESS
может быть >1.
22.03.2003: сделано, тривиальным образом: в
ArmAction()
делается
XmProcessTraversal(dial_w(bi),XmTRAVERSE_CURRENT);
.
17.03.2003: мышь работает. Хотя арифметика так пока и не вытащена.
Блин, прикол: при уведении мыши в узкий сектор "ничего" снизу, оно генерит "внепредельные" значения. А XV -- нет, хотя там никаких проверок на эту тему нету...
Через полчаса: разобрался. Осел! Есть в xv проверка -- в
DSetVal()
стоит RANGE()
, а я его просто не
заметил. В общем, вставил я загоняние угла в "отображаемый диапазон",
и все стало OK.
28.04.2003: наблюдена какая-то странность -- при выставлении диапазона в [0,360] градусов почему-то мышью ставится максимум 358.804 -- какие-то глюки с тригонометрией.
16.06.2003: какой зад: делал, делал, а получается -- плохоюзабельно это кручение канала мышью! Проблема в том, что юзеры щелкают на виджете, чтобы передать ему фокус, а он, "собака", начинает менять значение, чего юзеры совсем не ждут... Ну и что теперь -- отключать XmNtraversable у стрелочки? Предложение от Гусева: чтобы "тягаемость" включалась только тогда, когда юзер ткнул прямо в саму стрелку (или в небольшом пространстве вокруг нее).
15.09.2004: за компанию с изготовлением "умного" LOGD_{H,V}SLIDER и вследствие вытаскивания интерфейса текстовых полей в Chl_internals.[ch] решил "добить" и LOGD_DIAL. Итак:
widdepinfo
(через psp), так что теперь
поддерживается параметр "size=", а также флаги value
и input (аналогично slider'у).
CreateText{Value|Input}()
, что автоматом починило
вышеозначенную проблему.
15.09.2004: пообщался со Старостенкой на тему того, что есть в LabView, и что оттуда нужно нам.
Там имеется куча разнообразных control'ов, но функциональность оных вполне укладывается в то, что уже реализовано в Knobs, а именно:
- Текстовое поле ввода.
- Включено/выключено (чекбокс), в LabView имеет десятки разнообразных внешних видов.
- Лампочки (тоже с полдюжины вариантов).
- Селектор (combobox, menu -- как больше нравится).
- A-la scale/scrollbar -- тоже море разных: и просто "таскалки", и "термометры", в разных вариациях.
- Крутилки трех видов: собственно стрелочка (как LOGD_DIAL, но чуть иного вида), поворачиваемая ручка (стрелка не прыгает к мыши, а все крутится от того места, где взялись), плюс "узкий сектор сверху" a-la вольтметр.
И у scrollbar-like, и у крутилок по умолчанию имеется просто текстовое поле, в котором можно ввести "точное" значение.
Из того, чего в Knobs нету:
- Listbox.
- Таблица.
- Строковое поле ввода.
- Разнообразные декорации.
- Варианты селектора, scrollbar-alike и крутилок, когда вместо линейного числового диапазона указывается список пар "метка<->число", отображается именно метка, а "внизу" используются числа.
(Ох и чреватый же это вариант -- а что, если приходит число, которого нет в списке? Да и вообще -- много возни.)
Итого -- реально в Knobs достаточно полный набор, а что надо добавить -- так это "поворачиваемую ручку" (которая не прыгает) и, возможно, "a-la вольтметр".
Конкретно стоит предпринять следующие меры:
XmeDrawCircle()
.
BTW, проверено Lingvo'й: knob, dial и gauge -- вполне адекватные слова для обозначения терминов "ручка", "циферблат" и "измерительный прибор"/"шкала".
DialDraw()
, чтобы она все делала "умно": ей нужны
параметры "рисовать внутренности", "рисовать тики", "рисовать
highlight" (что-то еще?).
XtGetSubresources()
.
16.09.2004: сделано, теперь умолчательное
значение -- VALDPY_INPUT
.
16.09.2004: случайно заметил вчера, что "мышиное таскание" стрелочки dial'а не работает. Разобрался -- это произошло в момент перехода с {mindisp,maxdisp} на {rmins,rmaxs}. Там в вычислениях было "maxdisp-mindisp", а они по ошибке превратились в "rmins[]-rmaxs[]", вместо "rmaxs[]-rmins[]". Поправлено, все заработало.
17.09.2004: зарегистрировал (в cxdata.h,
Knobs_{widgetset.c,simple.c,_dial_widget.[ch]}) пару
идентификаторов: LOGD_KNOB
-- крутилка, которая объемная и
"таскается", и LOGD_GAUGE
-- будущий "стрелочный прибор"
a-la вольтметр.
17.09.2004: начал активно переделывать код в Knobs_dial_widget.c, чтобы он, во-первых, поддерживал множественные виджеты, а во-вторых, рисовал бы "круглости".
Проблем на этом пути встречено две. Во-первых, маета с определением
значений ресурсов -- Pixel'ы/GC и толщины; сейчас используется
комбинация XtGetSubresources()
с
XtVaGetValues()
(последний -- для имеющихся у
XmDrawingArea ресурсов). Во-вторых -- геометрия у X11, мягко говоря,
хромает: я ЗАДОЛБАЛСЯ подбирать сочетающиеся вложенные
окружности/диски/дуги, да еще так, чтобы это сходилось с собственной
полярной арифметикой.
Однако уже удалось сделать полную замену старому варианту
DialDraw()
, и начата реализация KNOB'а.
04.01.2005: удалил старый,
за-#if
'ленный вариант DialDraw()
. Ежели что
-- он имеется в STABLE/cx.20041231/.
17.03.2003: вставил использование IncDec в oldChl_text_widget.c, почти ничего не доделывая. Для этого в _text_widget.c добавил privaterec, и теперь при is_rw создается форма и два виджета (input и inputIncDec), и записываются в privaterec, а Create_m() возвращает форму (при !is_rw -- как раньше).
Поскольку IncDecB.c по нажатию нифига не делал, вставил туда
отсылку XKeySymToKeycode({XK_Up|XK_Down})
. При помощи
XSendEvent()
... Вот отсюда и прикол: если при этом
сфокусирован не client_widget, то эти кнопки ловит Xt и, считая, что
это не нашему клиенту, отдает другому. Выглядит забавно -- при нажатии
на уголки IncDec'а фокус начинает бегать по виджетам...
Надо теперь то ли слать XEvent впрямую, "руками" (слизав код из
xc/lib/Xt/Event.c::CallEventHandlers()
, то ли
принудительно фокусировать клиента (XmProcessTraversal()
)...
17.03.2003: угу, вставил перед XSendEvent'ом
if(XmProcessTraversal(client,XmTRAVERSE_CURRENT))
. Теперь
работает. Одна проблема (для виджета, не для Chl'я): если клиент будет
с XmNtraversalOn=False
, то это не сработает. Увы.
Собственно, это, похоже, верно с философской точки зрения -- нефиг
нарушать navigation model/focus policy Motif'а. Вообще, возможно, я
избрал очень просто реализуемый и элегантный, но весьма ограниченно
гибкий способ реализации сообщений "Increment/Decrement".
17.03.2003: есть еще одна проблема: почему-то оно
глючит при shadowThickness!=2: просто размеры ставятся неквадратные. В
общем, надо все же SetValuesAlmost()
реализовывать.
18.03.2003: по совету Ильи добавлена фича: щелчок
правой кнопкой на IncDec'е отсылается его client'у. Единственная
пока халтура -- надо бы и поля .x,.y
транслировать в
координаты клиента, чтобы менюшка по правой кнопке появлялась в
правильном месте, а то сейчас они оба :=0.
20.03.2003: "Халтура" исправлена, координаты
передаются локальные для клиента. Делается просто -- узнаются
координаты клиента относительно root window
(XtTranslateCoords(client,0,0,&cx,&cy)
), и потом
se.x=se.x_root-cx
, se.y аналогично.
18.03.2003: автоповтор мыши готов (что интересно,
именно с нуля, и ИЗ IncDec'а можно копировать В
LOGD_DIAL. Управляют автоповтором два ресурса --
XmNinitialDelay
и XmNrepeatDelay
, в точности
как в XmScrollBar'е, с теми же default'ами.
20.03.2003: был еще мелкий баг: в
IncDecBDisarm()
делалось просто
selfw->...timer=0
, хотя надо было еще проверить, если оно
!=0, то сделать XtRemoveTimeOut()
(сейчас -- делается).
Собственно, из-за этого, если несколько раз (N) быстро щелкнуть до
истечения первого initial_delay, то ставилось N таймаутов, которые
потом успешно отрабатывались (поскольку в момент их реального
срабатывания кнопка была уже опять нажата и timer!=0) и порождали еще
таймауты -- так что эффективно скорость повтора возрастала в N раз.
29.03.2003: и еще один баг имелся -- на этот раз
обнаружилось, что после добавления IncDecB, а точнее -- замены
bi->indicator
на input_w(bi)
перестала
толком работать колоризация. Причина была в том, что использовался
CommonColorize_m()
, а он работает именно с
bi->indicator
, каковой стал представлять из себя
форму-контейнер (ма-а-а-сенький кусочек ее был виден вокруг виджетов --
на месте highlinht rectangle).
Решение -- сделан свой, отдельный Colorize_m()
, имеющий
дело всегда с текстовым полем. Тут обнаружился второй прикол --
ведь bi->def{fg,bg}
брались тоже от
bi->indicator
(уже в CreateChannel()
), так
что пришлось добавить в text_privaterec_t
еще поле-флаг
are_defcols_got
, чтобы при первом вызове Colorize_m()
вычитывать fg/bg именно от текстового поля.
Вывод на будущее: похоже, у нас вместо
bi->indicator
надо иметь 2 разных widget-pointer'а: 1) тот, с
которым делать манипуляции по вставке в форму; 2) тот, к которому
применять colorize. Хотя все равно как-то несколько
неудовлетворительно получается -- ведь для реально композитных
виджетов, типа Dial, этот фокус так просто не сработает...
09.06.2004: УРА!!!!!!!!! Сделал реально работающую квадратность!!!
Разбирательство (с привлечением Антонова, а потом и без него)
показало, что тот способ -- следить за своей высотой и ставить ту же
ширину, работать не будет. В частности, из-за того, что XmForm (ох
кривой же это manager!) хреново работает с геометрией -- методом
query_geometry
child'ов не спрашивает (урод!),
set_values_almost
не использует (козел!), да и на
"принудительный" их само-resize реагировать не умеет.
И тут я попробовал вернуться еще к самой давней, трехлетней
давности идее (дата на самом старом из имеющихся
IncDecB.c -- июнь 2001г.). Смысл оной -- следить не за
своей высотой, а за высотой клиента. Поразбиравшись, решил добавить
"слежку" в метод realize
-- ведь он для incdec'а должен
вызываться ПОСЛЕ его клиента. И -- это получилось!
Конечно, при динамическом изменении размера клиента этого будет недостаточно. Но -- с чего бы вдруг полю ввода числа захочется динамически менять свой размер? Так что -- все окей.
(BTW, нижний аттачмент теперь совсем не требуется, так что из Knobs_text_widget.c он выкинут.)
Итого -- помечаем раздел как "done".
20.12.2004: было некрасиво, что при колоризации
имеется диагональная линия x=y того же цвета, что и фон -- например,
синяя; это мешало восприятию "треугольничков" как двух отдельных
кнопок. Вставил зарисовку этой линии цветом фона parent'а --
manager.background_GC
.
20.12.2004: кстати, обратил внимание на warning
"initialization from incompatible pointer type" -- оказалось,
что в поле set_values_almost
откуда-то взялось
XtInheritQueryGeometry
. Исправил -- поставил там
надлежащее XtInheritSetValuesAlmost
.
01.04.2003: угу, попробовал -- хрен-то там... Что-то оно так не работает, причем похожие же приколы при нажатии Ctrl+Ins. Надо разбираться с "клипбордами в X".
10.06.2004: вторая попытка -- более корректная,
но не более успешная: вместо "ручного" вызова action'а вставил в
SelectionCB()
вызов XmTextCopy()
. Хрен.
Самое забавное -- при этом действии еще и "xprop -spy -root" вылетает по SIGABRT. Как выяснилось, мои махинации тут ни при чем -- оно вылетает так всегда, даже если в Netscape-3.04g что-то выделить и руками нажать Ctrl+Ins. В любом случае, поскольку толку от моих пританцовываний никакого, пока что все они закомментированы.
06.01.2005: так, в качестве справочной информации: припомнил существование статейки от Jamie Zawinski -- X Selections, X Cut Buffers, and Emacs Kill Rings (локальная копия).
И совершенно очевидно, что если захочется-таки доделать
автоматическое копирование в клипборд руками, то надо это делать именно
"руками" -- самостоятельно копировать строку в
CUT_BUFFER0
.
19.01.2003: сделано, проверка вставлена в
SetControlValue()
, со всеми надлежащими (еще
эйдельмановскими) условиями: если значение вылазит за границу
допустимого менее чем на шаг стрелки, то просто возвращаем его на
границу, а если больше -- то еще и бибикаем.
30.01.2003: исправил ляп: проверки надо было
окружить условием if(fromlocal){}
, иначе она пыталась
корректировать даже полученные от сервера значения.
17.12.2002: готово. Только это у нас теперь не xmclients, а chlclients. Там есть директория DB, в которой хранятся файлы с именами вида db_СИСТЕМА.h, скрипт mkclient.sh, и "список описаний систем" SimpleClients.lst.
09.04.2003: замечание сюда от Ильи: этот индикатор является критическим -- если "отключившаяся" программа термостабилизации показывает T=24, а реально она уже давно 37, то это чревато.
И еще идея: можно не лампочку, а "синеть" (лучше -- желтеть) все окно.
22.07.2003: добавлено, по крайней мере, в первом приближении. Поскольку у нас теперь не 1, а N серверов, то из соображений "незахламления" пространства решено пока ограничиться лампочками. Эта сущность обозвана термином "LEDS". Лампочки могут иметь три состояния: "зеленое" -- сервер в полном порядке, "красное" -- связь с сервером потеряна, и "синее" -- аналогично "синим" каналам: канал соединения не порван, но данных давно нет (эти состояния определяются by cda).
Реализация, помимо поддержки в cda, состояла из 2 частей:
XhACT_LEDS
, который создает
контейнер-форму, аттачит ее точно так же, как tool-button'ы, и кладет
ссылку на нее в XhWindow.alarmLeds
.
CreateLeds()
, населяющая контейнер по информации от cda, и
UpdateLeds()
, обновляющая цвета лампочек по информации от
cda_status_of()
.
Имеющиеся проблемы:
23.07.2003: решение первой проблемы:
виджетам-LED'ам ставится XmNvalueChangedCallback
, в
котором принудительно делается XmNset:=True
.
Решение второй: в любом случае требовалось, вследствие "бага" в Xt,
приводящего иногда к "засыпанию" (сигнал возникает после
проверки на него, но перед select() -- оно и засыпает
"навеки"), все равно требовалась некая "тикалка", по образу и
подобию того, что уже есть в tsycam.c. Вот и был добавлен
практически в точности такой же KeepaliveProc()
,
запрашивающий свой собственный вызов через секунду. Он и вызывает
UpdateLeds()
. Основное отличие от tsycam -- то, что он
использует параметр closure
, и заказывает его в своем
вызове.
Недостаток/заметка на будущее: поскольку
алхимия с индикаторами -- CreateLeds() -- и с keepalive'ом делается в
PopulateWorkspace()
, то когда будем делать интерфейс для
"использования частей Chl" -- т.е., самостоятельного населения
workspace'а, то надо будет добавить туда hook для этой деятельности.
v=(текущая_частота*комплекс_работает + новая_частота*!комплекс_работает)Таким образом, при включенном комплексе будет игнорироваться свежеуставленное значение, а при остановленном -- браться.
(Доп. вопрос: а не нужно ли иметь спецкоманду
OP_CANCEL
, отрабатывающую при op_arg!=0?)
03.04.2003: О! А лучше -- занять для этого не "2", а "4", чтобы не конфликтовало с "disable flag" у LOGD_PUSH. И тогда можно будет потенциально все эти "интеллектуальные фишечки" у "non-value", т.е., "управляющих" виджетов (PUSH, ONOFF, ALARM, LIGHT(?), в отличие от TEXT, SCALE, DIAL, SELECTOR(?)) унифицировать.
И надо сделать дополнительный include-файл, в котором бы этот "протокол" описывался), например, src/include/cxval_proto.h, для #include'нья и драйверами, и Chl'ем.
Еретическая мысль: а может, все-таки подумать о некотором расширении cx-протокола, чтобы с данными передавалась бы и некоторая доп. информация (вроде "should disable", чтобы не вешать эти несвойственные функции на само значение). И всякие "коды ошибок" (a-la гусевские "расширенная информация об ошибке") все ж таки передавать надо. Хотя... если флаг "should disable" сюда еще хоть как-то вписывается, то "озелени чекбокс" -- едва ли. Или иметь какой-то общий флаг "все OK, пусть оно будет зеленым", применимый ко всем виджетам вообще?
03.04.2003: Wow! Я придумал -- самый простой
способ сделать олегов чекбокс зеленым -- ввести еще один LOGC,
например, LOGC_VIC
("Very Important {Channel|Control}"), у
которого бы background был зеленым (для привлечения внимания). А
пожелтение при отключенности -- уже сейчас есть возможность делать,
просто указанием желтого диапазона. Important! Надо это сделать
завтра и сразу же испытать при Олеге.
03.04.2003: Ура! Готово! Сделано именно так,
как запланировано: добавлен LOGC_VIC
, в
_internals.h добавлен COLOR_BG_VIC
, маппирующийся
на "бледно-зеленый", а в ChooseWColors()
добавлена
проверка, чтобы при bi->color
делалось
bg=COLOR_BG_VIC
.
На Олеге, к сожалению, испытать не удалось -- этот морж хреновый намылился в отпуск и обещает перевести управление синхронизацией на новый сервер только через месяц.
Еще вопрос: а надо ли делать каналы с color==LOGC_VIC при пожелтении/покраснении столь же интенсивными, как LOGC_IMPORTANT? Для этого надо будет всего лишь навсего вставить доп. проверку в пару к LOGC_IMPORTANT.
10.06.2004: наконец-то дошли руки реально
проверить фичу на обреченном db_rfsyn.h. Да, работает. Для
чекбокса (т.е., 0=off, 1=on) подходят такие параметры:
norm_range=[0.9,1.1] (все за пределами единицы -- желтое),
yelw_range=[0.0,1.0] (чтобы можно было вообще юзеру уставить 0 --
т.е., число за пределами norm_range; иначе его обрежет
SetControlValue()
...).
Единственная вылезшая проблема -- что ПЕРВОНАЧАЛЬНО (до получения данных) колоризация не производится.
Можно запараллелить этот механизм с механизмом "relax" --
переименовать relaxtime
в attn_time
, и ввести
доп. поле attn_colstate
, которому и приравнивать colstate
в ChooseColorState()
. Об одновременном возникновении состояний
COLALARM_RELAX и COLALARM_OTHEROP можно не беспокоиться -- первое
осмысленно только для readonly-каналов, а второе -- только для
write-каналов.
Проблема 1: что делать с каналами, которые меняются той же
программой, но другим логическим каналом (LOGK_CALCED, revformula)?
Ответ: проверка "чужих изменений" должна делаться в cda, которая
собирает изменения от всех лог.каналов. Отдавать поле "было чужое
изменение", по-видимому, надо так же, как и тэг -- доп. параметр для
cda_getphyschanval()
и cda_execformula()
.
Для формул флаг "remchg" считается аналогично тэгу -- берется максимум
(читай -- OR) флагов "remchg" из всех участвующих в формуле каналов.
Проблема 2: как детектировать "чужое изменение"? Сравнивать пришедшее в канал значение со значением, уставленным нами (тут как раз будет очень полезен "квант")? В какой момент считать, что мы к этому значению привыкли -- сразу же, выдав лишь один сигнал наружу, а там прикладная программа (in fact, Chl) пусть сама пять секунд отсчитывает? Не забыть по САМОМУ ПЕРВОМУ приходу данных сделать инициализацию, обойдясь без проверок.
05.06.2003: заменил механизм
relaxtime
на более общий
attn_time
+attn_colstate
(проверил --
SetRelaxing() по прежнему работает как надо), добавил цвет
COLOR_BG_OTHEROP="orange" и уже поддерживаемое состояние
COLALARM_OTHEROP.
И даже более того: вытащил содержимое SetRelaxing()
в
отдельную функцию SetAttnState()
, принимающую еще параметр
"state", а SetRelaxing()
теперь вызывает ее. И сделал
аналогичную SetOtherop()
.
Плюс, поскольку "переключение цвета" используется в двух местах --
_internals.c::SetAttnState()
и
_data.c::UpdateChannels()
, а там аж целых два if'а
(тождественность нового и старого colstate и наличие метода Colorize),
то оно вытащено в отдельный "присваиватель" --
SetColstate()
.
06.06.2003: Ну сделал я всю инфраструктуру в Chl и интерфейс в cda, проверил -- работает, теперь остался только 2-й проблем :-)
08.06.2003: вставил в
cda::DecodeData()
детектирование чужих изменений (двойной
вложенный if-then-else). В основном работает, но есть мелкий глюк --
если в "заметившей" изменения программе поменять уставку в изменившемся
канале, то почему-то на следующем цикле канал опять оранжевеет -- а не
должен бы.
09.06.2003: нашел, в чем глюк -- там вся логика
была во внутреннем if'е, если physmodified!=MODIFIED_USER
,
и вычисление flags сразу после делания уставки просто пропускалось.
Исправлено -- поставил в таком случае присвоение
flags=0
.
09.05.2003: идея: делать это не в tooltip'е, а в доп. popup-окошке, вызываемом из меню-по-правой-кнопке, а закрывается оно пусть pushbutton'ом. Это позволит обойтись без извратных требований на tooltip-widget, да и реализация это как бы более "правильная". (Если так уж прет сделать "tooltip'истое поведение", то можно повесить обработчик на LeaveNotify и делать в нем close этого окошка.)
09.12.2002: похоже, так (аналогично определению
Widget в Intrinsic.h:
typedef struct _WindowInfoRec *WindowInfo
-- т.е.,
изменения: вместо WindowInfo* будет передаваться просто WindowInfo.
Может, обозвать его HWindow?
10.12.2002: сделал -- два отдельных файла, Xh.h и XhP.h, первый
определяет
typedef struct _WindowInfo *XhWindow;
а во втором уже прописывается содержимое структуры -- аналогично
подходу к Widget/_WidgetRec в Intrinsic.h/CoreP.h
11.12.2002: все завершено. Компилируется,
программы-пользователи запускаются. Подход: есть определение
typedef struct _XhWidgetType *XhWidget;
-- все
публичные функции оперируют с XhWidget, а внутри преобразуют его к
Widget.
11.12.2002: ну сделал. Пока keine Probleme.
30.05.2003: сделано. В
ChlSaveWindowMode()
после каждого значения через
whitespace записывается еще и incdec_step
, а
ChlLoadWindowMode()
после значения также пытается
прочитать еще один double, и если он есть, то присваивает это значение
полю incdec_step
.
04.04.2007: ежу понятно, что это должно делаться теми кнопками "Быстро сохранить 'текущее'" и "Быстро восстановить 'текущее'", которые мы со Старостенкой обсуждали года три назад.
XhMakeMessage()
окну, созданному без флага
XhWindowStatuslineMask
. Результат -- оно валилось в core,
причем я потратил едва ли не час, чтобы вычислить причину. Резюме --
абсолютно очевидно, что XhMakeMessage()
должна проверять
наличие самой statusline.
window->statsLine!=NULL
, и если ==NULL, то сообщение
выводится на stderr (вместе с warning'ом).
11.06.2003: Разбирательство показало, что эта фича была внесена в curcx 26.12.2002, в попыхах, вместе с виджетом LOGD_LIGHT, но, в отличие от него, не была портирована в cx.
Портирование произведено. При этом в logchanbin_t
были
добавлены поля historybuf{,cap,used}
; в
_data.c::CreateChannel()
-- их инициализация
(скопирована из curcx); в
_data.c::UpdateChannels()
-- кусок обсчета (также
один-в-один скопирован из curcx; ограничен маркерами "MINMAX STAFF").
По поводу последнего -- "MINMAX STAFF": глядя на этот кусок, и на "DEVIATION STAFF", вытанцовывается некоторая закономерность -- реально это ведь не два отдельных if'а, а некий type-dependent селектор, управляющий произведением "нестандартного" обсчета для "нестандартных" каналов.
Кстати, в logchanbin_t
было укомментировано поле
history
-- артефакт от чуть ли не ucam (уж от oldcx --
точно), когда в дополнение к PopulateWorkspace() существовала еще
PopulateWithGraphs(), и это поле использовалось для хранения истории,
отображаемой графиком.
cx_strerror()
, туда вставил самостоятельный #include.
23.07.2003: в
oldChl_simple.c::ChlRunSimpleApp()
добавлено:
если phys_info_count<0
, то считается, что параметром
phys_info
передается указатель на "базу данных по physinfo
разных серверов".
24.07.2003: теперь пришло время подумать, как модифицировать SimpleClients.lst. Дело в том, что первое поле (system_name) является ключевым, и при помощи awk'а выдергивается LocalRules.mk'ом.
Так что лучше всего на роль "жертвы" подходит второе поле -- app_name. Теперь его формат сменится на
[*]app_name[@serverref]
Опциональный префикс "*" будет означать "этой подсистеме нужна вся БД
по physinfo", а "serverref" после "@" -- указывает default server этой
подсистемы.
Несколькими часами позже: все, сделал. Вставил изготовление allsystems.h в LocalRules.mk, плюс зависимость от него. Изготовил собственно mkphysdb.sh, генерящий этот allsystems.h. Подправил mkclient.sh, чтобы он правильно распознавал эти "*" и "@" и генерил соответствующий код.
Для подсистем, имеющих в описании "@", mkclient.sh также
устанавливает теперь сервер по умолчанию. Для этого не потребовалось
вносить какие-либо изменения в интерфейс ChlRunSimpleApp()
-- просто делается setenv("CX_SERVER",...,0)
.
Господи, неужели я наконец-то изготовил реальную возможность писать многосерверные chlclients? Ох... Надо теперь протестировать все компоненты многосерверности!
30.01.2003: reconnects сделано в
oldChl_data.c. Теперь вместо ChlSetConnection()
и сопутствующей атрибутики
(ChlNewDataProc()+ChlRequestData()
) в начале вызывается
ChlSetServer()
, и оно уже реализует все, что надо,
включая реконнекты через 1сек. после облома (и далее опять через 1сек.
при ECONNREFUSED).
Проблемы: поскольку раньше делалось
cx_connect()
, потом
ChlSetPhysInfo()+ChlPopulateWorkspace()
, а уж за ними --
PrepareAsync()
, то все было окей, а теперь, поскольку
ChlSetServer()
сам делает ChlRequestData
, то
пришлось ставить ChlSetServer()
в самом конце, т.е.
после SetPhysInfo..., что есть полный изврат -- оно же должно делаться
в самом начале, а потом уж из сервера будет качаться БД...
20.03.2003: ИК! Была а-а-а-громная проблема:
почему-то на самом первом цикле все показываемые значения были
бредовыми. Расследование показало, что проблема -- в этой самой
последовательности. Из каких-то соображений (кажется, я пытался
все-таки украсивить, и этот побочный эффект проглядел)
последовательность там стояла
ChlSetServer(),ChlSetPhysInfo(),ChlPopulateWorkspace()
.
Но прикол в том, что ChlSetServer()
вызывает
ChlRequestData()
, а тот запрашивает список каналов,
который формируется только в
ChlPopulateWorkspace()
! Так что
cx_getvset()
возвращал CEINVAL из-за count==0, и реально
нифига не читалось, а в памяти, куда должно было читаться, был мусор.
Использованное решение: последовательность сменена на
ChlPopulateWorkspace(),ChlSetPhysInfo(),ChlSetServer()
. В
общем, вид бредоватый...
27.03.2003: ЗАМЕЧАНИЕ:
SetPhysInfo()
может, строго говоря, стоять вообще в
произвольном месте -- главное, что ДО первого прихода данных.
Фишка в том, что в oldChl_data.c применяется сравнительно
неоптимальный (неиндексированный), но зато очень устойчивый механизм
использования phprops
: обращение к этому массиву идет
только при расшифровке полученных от сервера данных в
DecodeData()
и при отправке запроса на запись в
ChlRequestData()
-- в них обоих просто выполняется поиск
элемента p
, удовлетворяющего условию
p->n==np
.
31.03.2003: Был ЕЩЕ один прикол с
reconnects: таймауты "плодились": прямо во время
cx_connect_n()
успевало получиться ECONNREFUSED и в
какой-то момент вызывался notifier, делавший
XtNoticeSignal(=>FailureProc=>ScheduleReconnect), а
непосредственно после облома cx_connect_n() делался сразу еще один
ScheduleReconnect()
, так что "их становилось двое", а
потом -- трое, ну и т.д.
Решение проблемы -- сигнал все-таки запоминался в
reconnect_tid
, при нем !=0 заново
ScheduleReconnect/XtAppAddTimeOut не делалось, а из ReconnectProc оно
делалось =0. Т.е., при одном уже организованном таймауте второй не
ставится. Это решение один-в-один взято из
IncDecB.c&_dial_widget.c -- там была подобная проблема (точнее --
именно она там даже не успела возникнуть вследствие сразу правильного
подхода), и подобное же решение (пришедшее наполовину из
Xm/ScrollBar.c).
04.04.2003: после добавления поддержки "ручек" в oldChl_data.c стало, учитывая добавленные ранее reconnects, довольно прозрачно, как именно делать многосерверность: вынести всю эту алхимию с SetServer, получением и отправкой данных из _data.c в, например, oldChl_cxdata.c, и там уже будет почти готовая инфраструктура, которая в совсем скором будущем легко уйдет в lib/cxdata/. При этом, естественно, никакой привязки этой всей радости к XhWindow быть не должно.
Кроме того, надо рассматривать bi->phys
(и,
соответственно, операнд у OP_{GET,SET}P_I
как некий
"handle", впрямую не являющийся физ. номером канала. И в
*cxdata.c иметь функции типа
AddUsedChannel(SOME_SERVER+CHANNEL_REF)
,
SetChannelValue(???)
, неким образом получение текущего
значения канала, и т.д. -- т.е., оно должно давать "абстрактный"
интерфейс доступа к данным. Именно там же должны обрабатываться
формулы.
Пример устройства "handle": старший байт -- номер сервера во внутренней таблице (едва ли понадобится >256), остальные три -- линейный номер канала в этом сервере (2^24=16миллионов -- должно хватить). Если вдруг когда-либо этих лимитов станет не хватать, то переделать одну-единственную маленькую cxdata будет несложно.
Еще, для удобства: операнды в excmd_t
удобнее
рассматривать как union'ы:
и иметь для инициализации (сейчас -- для указания в DB/db_*.h) макросы, которые сами ставятtypedef union { double number; int chan_id; int handle; char *chanref; void *biginfo; } excmd_content_t;
cmd
в нужный код и надлежащим
образом инициализируют операнд.
GCC/C99 позволяет подобную инициализацию; например, макрос для операции сложения будет выглядеть так:
#define CMD_ADD_I(v) {OP_ADD_I, {.number=v}}
12.04.2003: тип excmd_t.arg
переделан -- теперь это тот самый union excmd_content_t
:
CalcExpression()
--
во-первых, dblstk[]
теперь имеет тип
excmd_content_t
взамен прежнего double
.
Во-вторых, там теперь везде явные обращения к типу используемого
аргумента; причем OP_{GET,SET}P_I
в отличие от всех
остальных используют .handle
, а не .number
; а
вот OP_{SET,GET}REG
-- именно .number, поскольку номер
регистра в принципе может и вычисляться.
clone_formula()
-- она вычитывает .chan_id
, а
пишет в "клонированный" вариант -- .handle
.
CMD_NNN_I(v)
и CMD_NNN
(причем в
определении последних все равно стоит .number=0, чтобы gcc не булькал
насчет "missing initializer".
Этот модуль использует префикс cda_
, и по дизайну очень
похож на cxlib. Вообще, похоже, надо будет назвать библиотеку даже не
cxdata, а именно cda -- Cx Data Access. А cxdata.h будет
содержать определения структур данных -- фактически, протокола; хотя,
по хорошему, надо иметь второй отдельный файл, в котором бы описывалось
именно все, относящееся к excmd -- ведь остальное содержимое
cxdata.h (logchan*, elem*, group*) для libcda совершенно
неинтересно.
Внутри библиотеки реализовано именно рассмотренное выше устройство handle -- старший байт на дескриптор сервера, остальные три -- на линейный номер канала.
Теперь используется "оптимальный" способ использования phprops --
они копируются в описание каждого физического канала при его
добавлении, и потом поиск по таблице уже не производится. Как
следствие "приведения в чувство" используемой модели (а конкретнее --
наличия отдельного вызова cda_run_server()
), в
ChlRunSimpleApp()
теперь наконец разумный порядок вызовов
-- ChlSetServer()
, ChlSetPhysInfo()
,
ChlPopulateWorkspace()
. Собственно, сейчас SetPhysInfo
просто обязательно должно быть до PopulateWorkspace.
Что теперь надо сделать:
oldChl_data.c
.
cda_run_server()
-- это
ChlPopulateWorkspace()
?
cda_add_server()
-- int
retry_on_failure
, и заряжать повтор попыток приконнектиться
прямо сразу (и в ConnectProc()
НЕ делать RequestData при
!is_running.
cda_register_formula()
, чтобы
отсеивать ошибочные коды еще на этом этапе.
18.04.2003: выполнен пункт о создании libcda -- создана директория lib/cda/, lib/oldChl_cxdata.[ch] перенесены туда и в include/, Makefile и #include в oldChl_* поправлены; lib/cxdata rmdir'нута.
17.04.2003: дык, как раз и сделано -- в предыдущем пункте.
20.03.2003: идея насчет ручек вообще: добавить в
logchan{bin,net}_t
дополнительное поле --
void *revformula
(реально --
excmd_t *
), в котором делать практически то же самое,
что в formula, но плюс чтобы были доступны еще 2 операции:
v
в SetControlValue()
); например, код команды
OP_QRYVAL='?'
.
OP_SETP='S'
.
Сегодня же отправлен запрос Карнаеву -- где узнать про их подход к реализации ручек. Ответ был -- "в понедельник"; видимо, он вернется из Японии.
Сегодня же поговорил с Федей маленьким. В некотором роде резюме:
надо помимо операции OP_POLY
иметь еще
OP_SPLINE='~'
, которая бы задавалась хитрой матрицей:
во-первых, сплайн (тот же полином) n-го порядка, а во-вторых -- этот
сплайн кусочно-гладкий: т.е., на интервале [begin,x1] один набор
коэффициентов, на (x1,x2] -- второй, и так до (xn,end]. Вопрос только
-- а что делать, когда "сплайнируемое" значение v находится вне
интервала [begin,end]? Естественно, диапазон ввода на виджете должен
ограничивать эти значения, но все же?
И не забыть для формул в обоих направлениях сделать работу с
переменными: OP_SETVAR='>'
и
OP_GETVAR='<'
(в идеале -- иметь в дополнение к массиву
"variables" еще массив тэгов, забиваемый в начале
CalcExpression()
нулями, который бы позволял отлавливать
использование переменной ДО ее инициализации).
26.03.2003: И еще: как бы этак вставить мудрые
проверки на корректность команд типа OP_GETP/OP_SETP
,
чтобы они всегда были "_I
". Кстати: а как насчет
просто не делать константы OP_GETP/OP_SETP
? А
только "_I
"?
И еще, в преддверии добавления стрелок, отображающися на кучу
каналов УРа: надо бы иметь OP_MOD
(а символ
'%'
уже занят под OP_DEVN
:-(). Плюс, а не
понадобятся ли какие-нить "условные конструкции" (простейший вариант --
тернарный оператор по образу C-шного cond?a:b
)? А что
будем делать, когда понадобятся функции, типа sin/cos/atan/...?
27.03.2003: include/cxdata.h: добавлены
константы OP_{SET,GET}VAR{,_I}
, OP_QRYVAL
(по
причине бессмысленности OP_QRYVAL_I отсутствует).
От OP_GETP*
оставлена только OP_GETP_I
(в CalcExpression()
стоит
"case OP_GETP_I & OP_code:
").
Добавлена константа OP_SETP_I='S'|OP_imm
.
27.03.2003: oldChl_data.c: в функцию
CalcExpression()
добавлены два параметра:
int is_write
/* Вызвано ли это для операции записи
*/ и double userval
/* Значение, введенное в
логическом канале */.
28.03.2003: OP_{SET,GET}VAR
переименованы в OP_{SET,GET}REG
(чтобы было понятно, что
это регистры, т.е. временные переменные).
28.03.2003:
oldChl_data.c::CalcExpression()
:
OP_QRYVAL
(тривиально) и
OP_SETREG/OP_GETREG
; пока ни то, ни другое не
протестировано.
OP_QRYVAL
отрабатывается только при
is_write!=0
.
OP_GETREG
стоит проверка инициализированности значения (по
массиву тэгов dblregsinited[]
), если нет -- то результатом
является NAN
.
28.03.2003: удален древний артефакт
OP_DEVN
, который все равно никогда не работал (да и не мог
бы при современной модели получения данных).
Освободившийся код '%'
занят под OP_MOD
,
реализация которого один в один скопирована с OP_DIV
(включая проверку на "сверхмелкий" делитель).
04.04.2003: запинывание работы "ручек":
datainfo_t.{double *newvals;
int *newmodified}
, инициализирующиеся аналогично прочим
sometype *physAAA
.
_internals.c::SetControlValue()
махинации
bi->newv=v; bi->modified=1;
заменены
на вызов _data.c::_SetPhysChan(bi,v)
.
_data.c::_SetPhysChan()
первоначально содержал
старый код (для проверки), который затем был сменен на новую версию,
модифицирующую уже di->{newvals,newmodified}[x]
.
ChlRequestData()
теперь ведет поиск и
извлечение измененных значений не по списку
di->chanbininfo
, а по массивам
di->{newvals,newmodified}
.
logchanbin_t.{newv,modified}
.
CreateChannel()
теперь
делается AddToPhysList()
не только по
OP_GETP_I
, но и по OP_SETP_I
.
logchan{net,bin}_t.revformula
CreateChannel()
код клонирования формулы вытащен в
отдельную функцию clone_formula()
, которая вызывается два
раза -- для formula и для revformula. (А вот проверка на
cmd==OP_SETP_I && !is_write
пока ничего
не делает...)
Кстати, там была совершенно дикая ошибка -- вместо
(src->cmd & OP_code) != OP_RET
стояло
src->cmd && OP_code != OP_RET
Почему оно функционировало -- уму непостижимо! Видимо, каким-то чудом
после записи с OP_RET всегда оказывалось нечто, выглядевшее как
src->cmd==0
.
logchannet_t.revformula
. Надо будет еще окучить Олега,
Чугуева и Доловова.)
CalcExpression()
вставлен код исполнения
OP_SETP_I
-- абсолютно аналогичный оному в
_SetPhysChan()
при LOGK_DIRECT (по-моему, это
"записывание" надо сделать макросом и использовать в обоих местах).
_SetPhysChan()
-- вызов CalcExpression() при
LOGK_CALCED.
Теперь надо провести проверку "не сломалось ли что" -- работа
коэффициентов r,d, проверить OP_{MOD,SETREG,GETREG}
,
вставить печать ругательств вместо /*Bark!*/
.
Доп. действия:
OP_RET
вставлена проверка, чтобы при
пустом стеке возвращать NAN -- это нужно для is_write!=0, как бы
"просто return", когда CalcExpression() как бы "void".
14.04.2003: проверил работу коэффициентов r,d -- работают, вроде не сломались.
25.04.2003: наконец-то поговорил с Карнаевым насчет ручек (увы мне, это он меня нашел, проходя мимо нашей двери с "посторонним вход запрещен"). Результат примерно такой:
У них есть два вида "считаемых" каналов: 1) матричные и 2) "формульные".Так что изготовленная мной схема с формулами является намного более мощной. (А что я, собственно, хотел -- у них же все обязано работать на Одрятах -- какой, там, нафиг, "вычислимый язык"?) Единственный минус моей схемы -- запись формулами каналов, являющихся матричными, несколько тяжеловата. Но это уж вопрос техники/интерфейса.1) Матричные -- это каналы, чтение которых работает путем перемножения вектора аппаратных каналов на вектор коэффициентов (displayed = H1*a1 + H2*a2 + ... + Hn*an), а запись из них производится по обратной матрице -- в каждый из аппаратных каналов уходит значение Hn=displayed*dn, причем есть "специальный", "расширенный" вариант, когда аппаратное значение определяется ДВУМЯ логическими каналами -- Hn=displayed1*d1n+displayed2*d2n.
2) Формульные же -- это readonly-каналы, которые обсчитываются некоей специальной, совсем отдельной для каждого такого случая, программой -- она считывает каналы исходных данных, производит вычисления, и помещает результат в BANK в некую специальную, не связанную ни с каким аппаратным значением, ячейку. Следствие 1: такие каналы появляются на экране на такт позже изменения их "исходных каналов". Следствие 2: в таких каналах отсутствует обратное преобразование.
3) Кроме того, есть и "кусочно-сплайновые" каналы, типа тех, что потребны Феде-маленькому, и для тех же самых целей -- управление токами магнитов. Причем Карнаев утверждает, что у них оне практически не используются -- поскольку границы между участками являются одновременно и местами, где требуется ручная настройка прочих параметров, а вообще вполне хватает ОДНОГО набора коэффициентов на все участки (или он имел в виду вообще линейную интерполяцию?).
Ну так что с нынешней реализацией -- "временный хак" ли это, или уже "нормальная поддержка ручек"? Сдается мне, что это уже правильный вариант...
14.05.2003: вообще-то эта фича давно (месяц?) работает, так что помечаем раздел как "done".
Пока остались неисполнены лишь федины запросы насчет кусочной аппроксимации.
27.04.2003: 14:30: поменял символ
OP_QRYVAL
с '?'
на '!'
, чтобы
вопросик освободить под оператор OP_CASE
.
27.04.2003: итак, кватернарный оператор
OP_CASE
. Аналог Си-шного тернарного оператора
cond?val_if_true:val_if_false
Условный синтаксис --
tested?val_if_less0:val_if_equal0:val_if_greater0
смысл --
т.е. очень похоже на старый фортрановскийif (tested<0) result=val_if_less_0; else if (tested>0) result=val_if_greater0; else result=val_if_equal0;
IF
, который
делал goto в зависимости от знака выражения -- меньше, равно, или
больше оно нуля. Реальная кодовая запись --
В реальности, естественно,CMD_PUSH_I(val_if_less0), CMD_PUSH_I(val_if_equal0), CMD_PUSH_I(val_if_greater0), CMD_PUSH_I(selector), CMD_CASE,
CMD_PUSH_I(selector)
будет
заменяться на что-нибудь типа
<добыть-значение>,CMD_SUB_I(const)
; таким
образом производится сравнение некоего значения с заранее известной
константой (или с другим вычисленным числом) -- достаточно вспомнить,
что в любом ассемблере/машинном коде команда cmp
эквивалентна команде sub
с выкидыванием результата. На
всякий случай (для извращенцев?) есть также OP_CASE_I
.
Зачем это понадобилось: дело в том, что у г-на Лебедева в управлении субгармоникой очень кривой пересчет из "градусов" в "биты УРа": там веса битов НЕ монотонно возрастающие -- есть дублирование (160,80,80,40,20,10,5,5), поэтому обычная конверсия при помощи остатка от деления попросту не работает.
Почему именно такая реализация: в идеале, конечно, это должен был бы быть тернарный оператор -- как в Си, но в Си в качестве условия стоит уже результат сравнения (0/1), так что для полной аналогии мне пришлось бы ввести дополнительный оператор типа "OP_CMP", который брал бы два значения из стека для сравнения, плюс у него был бы еще третий операнд -- вид сравнения (меньше, больше, меньше-равно, и т.д.). Таким образом, получающаяся пара "операция сравнения + тернарный оператор" просто более громоздка, к тому же пришлось бы проверять корректность операнда "вид сравнения". Так что кватернарный оператор эффективнее.
Есть, конечно, некая условность: в 90% случаев средний вариант (val_if_equal0) использоваться не будет, поскольку, как известно из школьного курса computer science, компьютерное представление вещественных чисел неточно, и операция сравнения их на равенство лишена смысла. Но для тех случаев, когда реально программа оперирует целыми, а не вещественными числами, такое сравнение все же имеет смысл, посему мы его оставили.
Вечер того же дня, 19:30: вах, сделал, работает! Реально работает в программе subharmonic, причем для лебедевских вычислений получились весьма монструозные формулы.
За компанию был "разрешен" оператор OP_DUP_I
, который
работает как сдвоенный OP_PUSH_I
одного и того же
значения.
Урок: надо аккуратно прописывать последовательность
вариантов, а то я разок лопухнулся -- сделал
CMD_DUP_I(m),CMD_PUSH_I(0)
вместо
CMD_PUSH_I(0),CMD_DUP_I(m)
-- видимо, сбивает с толку то,
что параметры в стек напихиваются в порядке написания, а вот селектор
-- последним, в то время как в операторе '?' он пишется первым.
Резюме: теперь мы вместо "простенького" стекового языка имеем
уже довольно навороченный, на котором далеко не каждый сможет писать.
Для полной комплектации не хватает только реального if
'а
(делается легко -- префиксным оператором, считающим, например, <0
за False, а >=0 за True, и влияющим на OP_SETP
и
OP_SETREG
-- на то, выполнить ли им реально запись; а
чтение параметров чтобы делалось всегда, дабы не портить стек) и
оператора цикла (а вот это уже реальный проблем, сего делать не
хотелось бы).
Резюме 2: а нафига, собственно, для этой лебединой задачи
нужен был условный оператор?! Первая проблема -- fmod()
,
всегда отдающий знак второго операнда -- делается отдельной функцией
(типа "mod2"). Вторая же -- да нету ее! Можно же всегда делать как
если бы имелось монотонное возрастание, просто идти не с младших
разрядов, а со старших. Т.е., безо всякого CASE делать нечто типа
где INTDIV -- это деление нацело, с отбрасыванием остатка (даже без доп. операции эмулируется как intdiv(x,y)=(x/y - ((x%y)/y)), хотя лучше иметь отдельную операцию OP_TRUNC). Эх.../* r1=(int)(r0/m) */ CMD_GETREG_I(0), CMD_INTDIV_I(m), OP_SETREG_I(1), /* r0-=r1*m */ CMD_GETREG_I(0), CMD_GETREG_I(1), OP_MUL_I(m), CMD_SETREG_I(0), /* c:=r1 */ CMD_GETREG_I(1), CMD_SETP_I(...)
28.04.2003: Увы мне, позор! Лишь попытавшись высказать вышеприведенные соображения Олегу, понял, что они попросту математически ошибочны! Ведь нельзя при отсутствии "монотонного возрастания весов разрядов хотя бы в два раза" делать INTDIV -- только вычитание! Пример: веса 160,80,80,..., число 320 -- что имеем? Правильно, intdiv(320,160)=2, таким образом, мы бы попытались в старший БИТОВЫЙ разряд записать ДВОЙКУ.
Конечно, можно было бы обойтись и без CASE, более хитрой формулой -- например, не просто делать INTDIV, а еще потом от результата делать copysign() и домножать сие опять на вес разряда -- так мы будем вычитать всегда 0 или вес, но никогда -- вес*N. Но для этого пришлось бы вводить лишний оператор OP_SIGNUM (может, когда-то он и понадобится, но вряд ли -- он уже сейчас тривиально эмулируется при помощи OP_CASE), а так мы теперь имеем весьма мощное средство -- условные вычисления.
CalcExpression()
ввести "таблицу операций", в которой бы указывалось, имеет ли смысл
immediate-операнд, сколько операндов из стека берется, так что можно
было бы делать проверку (и чтобы сразу брались a1,a2), и сколько
кладется (это чтобы проверять на stack overflow).
cda_del_server()
и
cda_stop_server()
(последняя должна будет дождаться
прихода заказанных данных? А то ведь иначе проблемы с realloc'ом того,
куда заказан приход данных -- при cda_add_physchan()
.). И
делать копии данных типа physinfo в свою память -- оно в тексте
отмечено "/*!...
".
18.07.2003: сделаны функции
cda_status_{lastn,of,srvname}()
. Первым параметром им
передается sid
. Первая отдает внутренний
номер последнего "доступного задействованного" соединения (0 -- само
основное соединение, 1..N -- дополнительные; при отсутствии
дополнительных возвращается 0). Остальные две принимают вторым
параметром внутренний номер (в диапазоне 0..status_lastn) и возвращают
соответственно статус (число --
CDA_SERVERSTATUS_{NORMAL,FROZEN,DISCONNECTED}
) и "имя"
сервера (типа "linac1:12").
Замечание: никаких таймаутов/watchdog'ов cda сама не ставит
-- просто по приходу данных от сервера в поле
serverinfo_t.last_data_time
запоминается текущий
time()
, а cda_status_of()
сравнивает текущее
время с запомненным, и если прошло более FROZEN_LIMIT=5
(секунд), то возвращается статус FROZEN.
Пока только сделано с нахрапу, но не проверено.
22.07.2003: убедился, что все работает. Заодно
введена доп. логика -- соединения, которые "!is_running
",
считаются за DISCONNECTED, даже если они и "is_connected
".
_data:UpdateChannels()
-- к месту ли оно там?
reconnect_on_initial_failure
.
cx_connect_n()
вместо
первоначального cx_connect()
?
cda_run_server()
-- вызывать
RequestData()
только если is_connected
(и
надо заиметь это is_connected
!).
ConnectProc()
-- вызывать
RequestData()
только если is_running
.
05.05.2003: Сделано, по пунктам:
rcn_on_ifail
.
cda_run_server()
перепахана, и теперь там две
слегка отдельных ветки исполнения в зависимости от
rcn_on_ifail
.
is_connected
; что интересно, в cxlib есть аналогичное поле
под названием isconnected
-- без подчерка. И даже более
того: все обращение с нововведенным полем было сделано по образу и
подобию того, что производилось в экспериментальной версии cxlib,
поддерживавшей reconnect'ы -- NCX, ~/work/ncx/src/lib/cxlib.c.
Там имелись "функции «атомарной» смены состояния"
SetStateTO_SSSS()
, и нас интересовали _Initial (:=0),
_Scheduled (:=0), _Established (:=1). Это нашло отражение в
cda_add_server()
-- :=0 (при rcn_on_ifail==0 -- :=1
сразу после cx_connect); ScheduleReconnect()
-- :=0;
ConnectProc()
-- :=1.
ConnectProc()
вставлена проверка -- делов-то
было...
06.05.2003: протестировал. Была пара мелких
багов (типа при обломе сразу при первоначальном cx_connect_n() надо
было вызвать FailureProc()
).
Теперь бы еще cda_add_server()
привести в пристойный
вид, в преддверии поддержки больших каналов...
И ВООБЩЕ-ТО, НАДО БЫ ЕЩЕ ПРОВЕРИТЬ, как оно себя ведет, если отсутствует удаленный, TCP-, а не локальный UNIX-сервер.
22.04.2003: требуется поддержка в cda для bigc -- конкретика:
cda_add_server()
.
30.05.2003: работы начаты. В обоих местах
cx_connect()
заменен вызовом функции
DoConnect()
, которая в зависимости от
si->type
делает cx_{connect,openbigc}()
.
А между тем вообще-то есть необходимость писать на cda совсем не графические программы -- во-первых, прокси для доступа сторонних (форточных) клиентов к CX; во-вторых -- консольные утилиты a-la cx-rdt.
В современной ситуации единственный выход -- вставить в консольную утилиту scheduler наподобие того, что имеется в cx-server'е (вопрос в сторону: а удастся ли выдернуть scheduler из cxsd_events.c+cx-server.c в совсем отдельную библиотечку, в параллель к fdiolib?).
Но это очень некрасиво, посему надо бы привести cda к универсальному интерфейсу наподобие того, что имеется в cxlib (и при том ловить возможности максимально унифицировать всю компанию -- cxlib, cda и tsycamlib).
07.05.2003: для staromakh пришлось
искать какое-то срочное решение. И обошлось без собственного
scheduler'а: Xt прекрасно работает без открытия каких-либо дисплеев и
создания окон: надо в начале программы вызвать
XtToolkitInitialize();
и
context=XtCreateApplicationContext();
, а после всей своей
инициализации -- XtAppMainLoop(context);
.
Но это не избавляет от необходимости сделать в будущем поддержку синхронного режима.
05.05.2003: сделано -- вставил в cda.h
макросы CDA_DECLARE_XT_ENVIRONMENT(app_context)
и
CDA_REGISTER_XT_ENVIRONMENT()
, в которые и перетащил
соответствующие куски oldChl_simple.c, оставив в оном лишь
вызовы макросов.
Заодно переименовал "cda_set_setters()" в "cda_set_environment()".
AllocateServerinfo()
и
ReleaseServerinfo()
; и вообще постараться унифицировать
менеджмент соединений с cxlib (CheckSid()
уже есть).
05.06.2003: к cda_getphyschanval()
и
cda_execformula()
добавлен доп. параметр --
int *flags
, куда будет сохраняться значение "этот
канал был изменен чужой программой".
08.06.2003: -09.06.2003: все сделано -- теперь есть и само детектирование изменений. Подробнее это описано в разделе про Chl.
13.06.2003: вчера была наблюдена проблема: при запуске программ vacuum и ringvac (работают с ИВА-16) каналы становятся оранжевыми. Проблема в том, что ИВА читается очень долго, и данные приходили не сразу, а лишь через цикл (кстати, подобная проблема может быть с любым блоком -- если программа пришлет запрос за несколько миллисекунд до окончания цикла, то ей достанутся лишь давние, "несвежие" значения, и она закэширует "не то").
Принятое решение: введено дополнительное поле
chaninfo_t.mdctr
, изначально равное 0 и сбрасывающееся в 0
же в cda_setphyschanval()
; оно увеличивается на 1 каждый
раз по приходу данных (i.e., в DecodeData()
), и "оранжевая
логика" отрабатывается только при tag<mdctr
-- т.е.,
только если присланное значение было померяно после отправки
запроса на запись.
ЗАМЕЧАНИЕ: вообще-то и вторую ветвь if'а -- кэширование -- стоит выполнять только при приходе "сравнительно свежего" значения: какой смысл нам кэшировать протухшие данные, которые гарантированно вот-вот заменятся нормальными? А для этого надо флаг "notfirstcycle" сделать per-channel. Who-o-o-psь.
14.06.2003: сделал я все вышеописанное -- и флаг
chaninfo_t.isinitialized
, и загнал "логику" в ветвь, когда
tag<mdctr
. Надо бы теперь это протестировать.
15.06.2003: проверил, работает. Почти как надо
работает -- но именно "почти": проблема в том, что у свежезапущенного
сервера (якорь) Хорошее
решение -- заставить сервер для никогда- Ну хорошо, это с сервером сделано
( В таком случае у нас будет три независимых уровня "защиты от
несправедливого оранжевения":
Единственное, что смущает -- это ж получится такая многократно
дублированная система, что при возникновении некоторых глюков мы их
симптомов даже не увидим -- они будут успешно отфильтрованы. Как-то
даже боязно лишать себя диагностики...
current_cycle=1
, а все c_time[]
-- 0,
и разность между ними мелкая -- если cda хоть два раза получала от него
данные ранее, то mdctr>1
, и присланное значение
считается "годным для сравнения", а это не так -- та же ИВА не успевает
(по крайней мере, при отключенной серверной
предвыборке-MAX_TAG_T
, тогда, поскольку у нас стоит
сравнение "строго меньше" -- tag<mdctr
-- тэг
всегда будет "больше либо равен mdctr", предотвращая
оранжевение.
current_cycle:=MAX_TAG_T
), а как насчет
изготовить в cda дополнительный рубеж защиты -- сбрасывать
mdctr
в ConnectProc()
или
FailureProc()
, тем самым сбрасывая логику в начальное
состояние, "как при запуске"? Вроде бы, с "висящей недовыполненной
записью (physmodified!=MODIFIED_NOT)" это интерферировать не должно --
при записи mdctr
сбрасывается в 0.
Даже если выключить любые два, то третий все равно воспрепятствует
"неверному оранжевению".
mdctr
в 0, дабы не-читанные каналы не
рассматривались.
16.06.2003: ну сделал -- в
FailureProc()
вставлен кусочек кода, сбрасывающий все
mdctr
в 0. Правда, пока не проверял.
18.06.2003: в письме Олегу присутствовало следующее описание "логики" оранжевения:
Привожу это здесь, поскольку это написано более-менее человеческим языком (вместо Си :-), плюс, здесь прослеживается объяснение той логики, которой я руководствовался, изображая тот код.Алгоритм оранжевения каналов (подробнее -- см. src/lib/cda/cda.c::DecodeData(), раздел <<the "orange" intellect>>): если пришедшее значение отличается от того, которое было у нас запомнено, на "квант" или более, то оранжеветь. Доп. условия: - "запомнено" -- запоминается а) значение, которое уставил юзер путем ввода в поле; б) в самом начале, при пуске программы -- текущее значение, отданное сервером. - "запоминание" (и сравнение для оранжевения) НИКОГДА не делается, если полученное от сервера значение, судя по тэгу, старее момента, когда cda решила, что ей нужно запомнить/сравнить -- т.е., при старте программы она дождется, когда же наконец придет значение, померянное ПОСЛЕ ее запуска, а при записи юзером -- когда же придет значение, померянное ПОСЛЕ выдачи команды на запись.
CDA_FLAG_OTHEROP=1<<0
".
И, заодно, дать константам *_ERROR
префикс
"CDA_
".
14.06.2003: сделал и то, и другое, и еще ввел
специальный тип -- cda_chanflags_t
. И _data.c поправить не
забыл.
Надо бы не забыть подправить staromakh.c.
25.06.2003: staromakh.c подправлен.
ExecFormula()
. Смысл
-- "локальные" каналы (например, для Фединой программы -- высчитал
токи, а потом "актуализируешь" их кнопкой [Прописать].
Вопрос: глобальные для всей cda, или в рамках сервера (serverid)?
16.06.2003: связанная вещь: хотелось бы, чтобы по
модификации таких "локальных каналов" связанные с ними каналы могли
сразу обновляться. Для этого потребуется а) ввести доп. команду --
например, CMD_REFRESH
, а главное -- б) дергать
"event_processer" программы-клиента, дабы вызвать там обновление.
Проблема заключается в том, что в Chl к событию "приход данных"
привязано несколько слишком много -- в т.ч., всякое бибиканье (к
счастью, хоть "релаксация" и "оранжевость" там делаются по времени),
плюс проверка/сброс флагов wasjustset
, сдвиг "истории"
(упс... как с ней-то в этом смысле обращаться?).
Решение проблемы вообще-то несложно -- ведь event_processer'у
передается еще некий "код-причина", пока неиспользуемый, так можно
начать его использовать, и иметь для реального прихода данных и
"программного обновления" разные коды. Так что
UpdateChannels()
сможет честно пропустить "поцикловые"
действия.
08.07.2003: да, делать глобальные регистры надо. Причем, поскольку манипуляции с ними совершенно идентичны работе с временными регистрами, то очевидно, что надлежит реализовывать их одним и тем же кодом, модифицировав его (код обработки OP_{SET,GET}REG) для обращения к массивам regs и regsinited не напрямую, а по указателям, которые и выставлять в зависимости от типа регистра. А коли так -- то проще пареной репы сделать и server-wide, и library-wide регистры.
Есть лишь один вопрос: для введения сих операций потребуется 4 дополнительных команды -- OP_{SET,GET}{SRV,LIB}REG. Соответственно -- 4 символа. А ведь, поскольку регистров нужно немного, то можно рассматривать номер регистра как 2 16-битных поля: младшее -- собственно номер, а старшее -- "селектор", определяющий локальный это регистр (0), server-wide (1), или library-wide (2).
Такой подход удобен, но имеет недостаток: становится затруднительно обращение с регистрами как с массивом -- поскольку придется прибавлять к индексу еще и селектор.
Впрочем, фиг с ним, с этим недостатком -- реально индексирование регистров не используется (по крайней мере, пока), а при надобности прибавить селектор типа -- совсем не проблема.
В общем, выбран вариант с унифицированными опкодами.
Занятное следствие: макросы CMD_{SET,GET}{SRV,LIB}REG_I
созданы (они прибавляют селектор к номеру регистра), а вот их
не-_I
вариантов не существует -- поскольку все делается
обычными {SET,GET}REG.
08.07.2003: Хохма: а ведь формулы-то не привязаны
к какому-либо серверу, так что в рамках некоей "формулы" понятие
"регистр сервера" становится... ммм... абстрактным... Надо вставлять
понятие "default server"? И куда же? Варианты: 1) дополнительным
первым параметром в cda_execformula()
; 2) принудительно
первой операцией любой формулы прописывать что-то типа "установить
сервер по умолчанию".
Выбран второй вариант -- первой операцией клонированной
формулы проставляется специальный опкод __OP_SETDEFSID_I
,
с параметром defsid
, а cda_execformula()
проверяет, что первым идет именно этот код, и вычитывает его параметр
отдельно, еще перед циклом.
08.07.2003: по ходу дела поменял типы массивов
*_regsinited[]
с int
на char
--
нефиг зря расходовать по 300 байт на каждое соединение.
09.07.2003: насчет обновления данных при записи в глобальные регистры:
cda_execformula()
вернет 1 вместо обычного 0. Так что прикладная программа (читай --
oldChl) сможет сама вызвать Update() с флагом "synthetic=1".
Смысл -- это предотвращает потенциально возможную бесконечную итерацию, когда некая формула делает "CMD_REFRESH", оно вызывает (пусть даже по кумулятивному флагу -- в конце execformula) event_processer, тот запускает обновление, а одна из тамошних формул также содержит обновление, ну и так далее. Конечно, мы запрещаем REFRESH в read-type формулах, но лишняя предосторожность не помешает.
Соответствующие изменения в oldChl_data.c внесены. Особо
занятным там является получение ссылки на окно в "callback'е"
_SetPhysChan()
-- при помощи XhWindowOf()
.
OP_REFRESH='#'
. Это
"условная команда" -- она принимает один параметр, который если !=0, то
выполняется refresh, иначе это -- просто NOP. Очевидно, по большей
части сие будет использоваться в варианте OP_REFRESH_I(1)
.
09.07.2003: опять насчет типов регистров: а ведь реально-то "глобальные", хоть server-wide, хоть library-wide, регистры совсем не нужны, они даже вредны! Ведь как мы сможем заставить договориться несколько разных группировок о неперекрывающихся номерах глобальных регистров? Никак!
А нужны -- некоторые типа "local", причем этот "локальный" блок
регистров должен указываться клиентом в cda_execformula()
,
и тот же клиент может при работе с каналами одной группировки
сбагривать один блок регистров, через который ее разные каналы/элементы
могут обмениваться данными, а другой группировке -- ее собственный блок
регистров, независимый от первого.
17.07.2003: кстати, при этом можно будет
избавиться от хака под названием "__OP_SETDEFSID_I
".
18.07.2003: вроде заменил концепции "{SRV,LIB}
regs" на "LCL regs", когда набор регистров предоставляется клиентом.
У-ф-ф, муторно. Пришлось потрогать 1) cxdata.h -- определения
OP_ARG_REG_TYPE_*
и CMD_SET???REG_I
; 2)
cda.c -- собственно махинации с регистрами; 3)
oldChl_data.c -- основной клиент, там были добавлены поля
datainfo_t.localregs{,inited}
; 4) staromakh.c, в
котором также введена per-server поддержка локальных регистров -- для
этого пришлось организовывать back-references из
chandesc_t
на его базовый serverdesc_t
.
Заодно за-#if0'ены все вещи, связанные с
__OP_SETDEFSID_I
.
Теперь надо это все тестировать. Для подобных целей хорошо подойдет fedcoils.
30.07.2003: __OP_SETDEFSID_I
выкинут
окончательно.
08.07.2003: в текущий момент для этого
понадобятся два дополнительных кода операции -- типа
"OP_{GET,SET}P_BYNAME_I", которым указывалось бы текстовое имя канала
(благо, поле char *(excmd_content_t.chanref)
уже давно
предусмотрено). А после резолвинга эти опкоды заменялись бы уже на
обычные OP_{GET,SET}P_I.
Разумным было бы занять под эти коды P_BYNAME_I те же буквы (get=p, set=s), что и в P_I, но на другом регистре. Проблема: 's' занято под OP_NEG.
Решение проблемы: для OP_NEG был бы идеален символ "плюсминус",
которого нету, но самый визуально похожий на него -- это '='
(который, кстати, использовался в качестве символа операции
PDP/LSI-11 "neg
" в ассемблере Ляпидевского на БК-0010, а
именно этот ассемблер является в некотором роде синтаксическим предком
нашего формульного языка; сие дает дополнительное мнемоническое правило
;-). Но вот незадача: '=' занято под OP_RET. С другой стороны, у
нас свободна '.' -- вот ее и займем под OP_RET, благо, подобная
мнемоника используется в SMTP (Точка,CR-LF -- конец письма).
Короче -- сделано: теперь OP_NEG='='
,
OP_RET='.'
, и добавлены два новых кода --
OP_GETP_BYNAME_I='p'|OP_imm
и
OP_SETP_BYNAME_I='s'|OP_imm
. К последним изготовлены,
естественно, и соответствующие макросы.
17.07.2003: начал делать собственно "многосерверность" -- возможность ссылаться из формул на каналы по имени вида "server_ref!chan_n". Имеются некоторые буераки на пути к решению:
cda_register_formula()
внахаловку проходится по всем
зарегистрированным серверам и если находит такой, у которого поле
srvrspec
совпадает с указанным в ссылке, то берет его sid,
а если таковых нету -- сама создает новое соединение.
Правильнее было бы завести понятие "reference count соединения" и
поддерживать нормальный share'ing соединений между "прямыми",
заведенные явными запросами cda_new_server()
, и "кривыми",
создаваемыми для формул. При этом понадобится вместо "notifier" ввести
понятие "callback list". Или иметь оба -- primary, который и есть
notifier, и дополнительные -- которые callback chain?
cda_register_formula()
вызывается, в
принципе, в произвольный момент, а регистрировать каналы можно только в
stopped-соединениях, то имеем еще косяк. Реально -- сейчас
предполагаем, что формулы регистрируются всегда ДО запуска всех
соединений. Соответственно, надо будет в
cda_run_server()
делать run и всем его доп. серверам.
Вообще-то, совсем "правильный" способ -- забить на ограничение "добавлять каналы можно только к остановленным серверам". А делать это можно так -- иметь double-buffering, и помнить "запущенные буфера", и "новые буфера", и иметь флажок "new_channels_added", так что по приходу данных делать free() старых буферов, копировать новые указатели на место старых, и сбрасывать флажок. Естественно, при взведенном флажке повторять клонирование буферов уже не требуется -- все уже на месте.
Да, это есть "the right thing (TM)", но трудоемковато -- сильно изменит идеологию. Так что отложим сие изменение на некоторое "потом".
CMD_{GET,SET}P_BYNAME_I
выглядит
преотвратнейше. Фи.
GetChanSpecParts()
, почти вчистую скопировав ее
содержимое из tsycam.c. Вывод: явно надо выпихивать ее в
какой-нить cx_macros.h (а лучше -- сразу в cxlib.c --
"cx_parse_chanref()
"), да и сам парсинг надо сделать с
проверками на ошибки (конкретнее -- вставить strtol()
вместо тупого atoi()
).
Хе -- сразу же и выпихнул ее в cxlib :-).
21.07.2003: вставил в
cda_run_server()
автоматический запуск всех auxservers.
23.07.2003: а ведь есть еще одна проблема с многосерверностью -- physinfo. Ведь оно задается явно для каждого создаваемого соединения, а "автоматически создаваемые" соединения остаются без оного.
Очевидная идея: в конечном-то итоге physinfo должно будет вычитываться из сервера, сразу после установления соединения, так? Т.е., браться из общей БД. Так можно же ввести такое понятие, как "глобальная БД по physinfo" -- массив, состоящий из структур вида
{char *srv, physprops_t *info, int count}
Тогда cda_new_server()
может самостоятельно произвести
поиск нужного ей physinfo, и явное указание будет даже необязательно.
Парой часов позже: сделал. Только теперь надо бы а) проверить; б) вставить какую-то поддержку этого в oldChl_simple.c и/или в chlclients/mkclient.sh.
28.07.2003: добавил механизм "callback list" --
функции cda_{add,del}_evproc()
. Второй также передается
proc,userptr
, и он удаляет по полному соответствию.
Замечание: пока понятие "callback list"
сосуществует с "notifier". Никаких реальных причин на эту тему нет --
вполне можно прямо из cda_new_server()
вызывать
cda_add_evproc()
.
Несколькими часами позже: сделал, что для всех auxservers
добавляются внутренние callback'и, которые делают
NotifyClients(,CDA_R_AUXDATA)
. Вставлена поддержка для
этого и в Chl.
Еще чуть позже: совсем убрал понятие "primary callback" -- теперь есть просто некий список callback'ов.
30.07.2003: насчет double-buffering'а: принят следующий алгоритм:
phys{count,codes,tags}
, и используется стратегия
"copy-on-write". Т.е., при добавлении канала в момент, когда запрос
послан (т.е., реаллокировать память нельзя), текущие значения
копируются в shadow/old-buffer, так что с новоаллокированными буферами
уже можно химичить. По приходу запроса результаты копируются из
shadow/old в текущий, и старые буфера высвобождаются.
BTW, это напоминает поведение GCC+ядра при создании бинарника, если он сейчас исполняется: делается unlink() старого образа, но он остается на диске "анонимно", и новая версия пишется в другой inode, а когда "старый экземпляр" завершится, то его образ физически удаляется с диска.
old_phys{count,codes,tags}
(physlist
дублировать не надо, поскольку он не является
return-параметром функции cx_getvset()
).
double_buffer_used
-- назначение очевидно.
req_sent
взводится при отправке запроса, и
сбрасывается при получении данных и обрыве соединения.
{Activate,Fold}DoubleBuffer()
.
Double-buffering подключается только при реальной необходимости: т.е., когда было отведено хоть что-то (т.е., текущее count!=0), отведенное под каналы место закончилось, и req_sent.
Как водится, нифига не протестировано.
08.10.2003: при добавлении "physstats" нашел
прелестный баг: в ActivateDoubleBuffer()
новые буфера
аллокировались, но в si->phys*
эти указатели не
прописывались. Wow, how smart!
cda_add_server()
-- избавиться от
запутывающего словца "add": add к чему?
08.07.2003: сделал. Заменил "add" на "new", так
что теперь у нас совсем a-la-C++ -- там есть операторы new
и delete
, а у нас -- cda_new_server()
и
cda_del_server()
. (Первоначально была мысль заменить
"add" на "create", но оно имеет другую длину, и это нарушило бы
красоту. А так -- все мило и ровно.) Незабвенный
staromakh.c тоже модифицировал.
CMD_RET_I()
вместо OP_RET_I
стояло OP_RET
. Как показали раскопки в
{BACKUP,STABLE}/, это было с самого начала появления
CMD_xxx()
. Баг найден при написании fedcoils --
там отладки ради использовалась CMD_RET_I(0.0)
, а
результат "почему-то" отображался nan
.
21.07.2003: А что делать? Видимые варианты:
RequestData()
вызывает
cx_getvset()
, а
cx_getvset()
=>AddFork()
проверяет что
count>0
. Возможное решение -- чтобы в
RequestData()
при si->physcount==0
пропускать cx_getvset()
, а уж и cxlib, и сервер прекрасно
отрабатывают "пустые" пакеты запросов -- там стоят корректные циклы.
По здравому размышлению ясно, что пока, по принципу "на все времени все равно не хватит" стоит пользоваться первым вариантом, он ведь работает -- и ладно. Второй выглядит непривлекательным -- принципиальных преимуществ по сравнению с первым у него нет, а только лишняя работа плюс уродование кода. Третий же -- это на "светлое будущее", если сильно припрет.
21.07.2003: вставил в RequestData()
проверку, что требовать данные только при
si->physcount!=0
.
21.07.2003: BTW, есть еще один минус от такой "централизации": ведь индикаторы статуса серверов-то обновляются по тактированию, т.е., по приходу данных основного сервера, а если он в отключке -- то ту-ту. И даже более того -- ведь мы не увидим сдыхания даже основного соединения!
cda_register_formula()
, как водится, вначале стояло
"serverinfo_t *si = servers + defsid;
", а в серединке, при
разборе OP_{GET,SET}P_BYNAME_I
вызывалась
cda_new_server()
. Последняя делала
realloc()
, так что si
, естественно,
становилась бессмысленной.
22.07.2003: естественно, исправлено. Очевидным
образом. Общие соображения/
cda_new_server()
надо
ре-кэшировать si
. Вообще -- это кэширование в случае
"потенциально реаллокирующегося" массива является довольно
опасной/скользкой практикой, которую надо применять с осторожностью.
cx_strreason()
, которая возвращала бы читабельное описание
кодов CAR_xxx
(аналогично cx_strerror
). Это
нужно как минимум для реализации reconnect'ов в Chl, где делается
fprintf(stderr,...) причины обрыва соединения.
28.02.2003: готово -- что там было делать-то...
09.04.2003: что сделано:
OPTION_MUST_SET_PID_FOR_ASYNC=1
-- надо ли
перед переводом сокета в асинхронный режим устанавливать pid получателя
сигнала (в Cygwin мало того, что не надо, так еще и соответствующий
символ SIOCPGRP не определен).
OPTION_MUST_USE_NONBLOCK_FOR_ASYNC=0
--
должен ли сокет быть неблокирующимся, чтобы приходил SIGIO.
OPTION_CYGWIN_BROKEN_WRITE_SIZE=0
-- какой
максимальный объем можно скармливать write()
; 0 --
неограниченный.
USE_IOCTL_FOR_ASYNC:=1
,
MUST_SET_PID_FOR_ASYNC:=0
,
MUST_USE_NONBLOCK_FOR_ASYNC:=1
,
CYGWIN_BROKEN_WRITE_SIZE:=32000
.
cxlib.c::install_sigio_handler()
: наконец-то
переделана для использования OPTION_SA_FLAGS
вместо
#if'ов.
cxlib.c::TurnToAsync()
: очень перепахана:
оттуда убраны #if defined(OS_...) и заменены на
#if OPTION_USE_IOCTL_FOR_ASYNC
, плюс вставлен
#if OPTION_MUST_SET_PID_FOR_ASYNC
. Функция стала
похожа на Франкенштейна -- сплошные кусочки...
11.04.2003: корректности ради в
TurnToAsync()
теперь делается не просто
fcntl(fd,F_SETFL,FASYNC)
, а вначале читается текущее
значение флагов, и делается "flagsv|FASYNC"
.
Получасом позже: сменил этот код на более "чистый" и "общий"
set_fd_flags(fd,FASYNC,1)
.
05.08.2010: ага, только называть-то функцию надо
было "set_file_flags()" -- поскольку там F_SETFL
,
FiLe flags, а не FileDes flags (которыми заведует
F_SETFD
).
Разница в том, что:
FD_CLOEXEC
.
Так что, по-хорошему название "set_fd_flags()" должно было принадлежать функции, махинирующей с FD-flags, а этой надо было давать название "set_file_flags()". Но теперь уже давным-давно поздно.
И, кстати, я не единственный: функция с таким же названием нашлась в
исходниках dd, и она делает практически то же самое; строка же
"set_file_flags" встречается почти исключительно в описании BSD'шной
chflags()
(а никаких "set_fl_flags" Google, к счастью, не
знает). Так что -- ничего страшного :-)
11.04.2003: MUST_USE_NONBLOCK_FOR_ASYNC:
вставлена поддержка "необходимости иметь сокет неблокирующимся для
прихода SIGIO": в файл добавлена уже широко распространенная в
дереве функция set_fd_flags()
и вставлены ее вызовы в
нескольких местах, окруженные проверкой
#if OPTION_MUST_USE_NONBLOCK_FOR_ASYNC
:
SendRequest()::n_write()
стоит сброс в 0 до, и
взведение в 1 после.
ConnectTo()::connect()
.
Плюс, сразу после этого, еще внутри "скобок", стоит
CheckPending()
-- ведь сигнал-то может придти сразу после
(реально -- внутри) connect()'а.
11.04.2003: CYGWIN_BROKEN_WRITE_SIZE:
слегка модифицировал код misc_macros.h::n_write()
: заменил
вызов
на хитрую конструкциюresult = write(fd, buf, rest);
Таким образом, под Форточками мы пишем в дескриптор слайсами поresult = write(fd, buf, #if OPTION_CYGWIN_BROKEN_WRITE_SIZE != 0 rest >= OPTION_CYGWIN_BROKEN_WRITE_SIZE ? OPTION_CYGWIN_BROKEN_WRITE_SIZE : #endif rest );
OPTION_CYGWIN_BROKEN_WRITE_SIZE
.
19.04.2003: первая попытка компиляции под Cygwin/Win98. Потребовались изменения в sysdeps:
include/sysdeps/os_cygwin.h:
OPTION_HAS_PROGRAM_INVOCATION_NAME:=0
-- не-GNU там
линкер, так что и фича эта отсутствует. Плюс пришлось закомментировать
определение OPTION_ENDIAN_H_LOCATION
-- почему-то в
инсталляции Cygwin на гериной машине этот файл отсутствует. (Доп.
разбирательство показало (письмецо
"Xarch.h
includes nonexistant <machine/endian.h>"), что,
возможно, надо брать файл asm/byteorder.h. Кстати, а ведь при
OS_SOLARIS делается примерно то же самое -- sys/byteorder.h;
может, и OS_CYGWIN присовокупить туда же?)
include/cx_sysdeps.h: пришлось добавить
"||defined(OS_CYGWIN)
" к
#elif defined(OS_SUNOS)
в проверке на endian.h, чтобы
в отсутствие этого файла порядок байт также определялся позже через тип
процессора.
А в конечном итоге -- МАТЬ ВАШУ, ДА ОНО РАБОТАЕТ!!! Попробовал cx-rdt и cx-setchan -- функционируют!!! Чес-слово, не верилось, что сие запустится... Все, надо теперь разбираться, как подшивать объектники, сгенеренные Cygwin'ом, к нативным форточным программам -- к примеру, CVI и MSVC...
И еще возникла такая еретическая мысль: а все-таки, ведь единственным компонентом клиентских программ, имеющим привязку к конкретной ОС (за вычетом Xh/Chl, но там решение проблемы известно), является именно libcx. А что, если попробовать ее сделать так, чтобы она компилировалась как Win32-native code? А?
Дополнение 22.04.2003: чем больше я разбираюсь с "Cygwin,
как оно устроено, интеграция Cygwin и MSVC/CVI", тем сильнее
желание устроить Win32-native компиляцию. Видимо, при этом надо будет
где-нить в начале cxlib.c определять "среду компиляции", и в
зависимости от нее определять некоторое количество функций/макросов,
которые потом использовать в коде. А в самом коде поизбавляться от
#if
'ов вовсе (таким образом, франкенштейнистость будет
локализована). А в libcda так и вовсе ни единого #if нету, и весь В/В
делается через libcx -- так что все пучком.
20.04.2003: попытался поразбираться с endian
adventures... Бррр... В общем, cygwin'овский asm/byteorder.h
определяет только __LITTLE_ENDIAN
, что нам без толку.
Зато там есть sys/param.h, содержащий всю троицу --
{BIG,LITTLE}_ENDIAN,BYTE_ORDER
, с комментарием -- что оно
там для вящей радости autoconf'а. Может, сделать
OPTION_ENDIAN_H_LOCATION:=<sys/param.h>
?
20.04.2003: так и сделал -- работает, а в include/cx_sysdeps.h отдельную проверку на OS_CYGWIN из пары с OS_SUNOS убрал.
10.04.2003: сделано. Для этого добавлена возможность указывать "отрицательный" номер сервера, который при этом рассматривается (после отбрасывания минуса) как точный номер порта. Проверка на возможность осуществления соединения по unix при этом подавляется.
В cxlib.c::GetServerSpecParts()
при этом была удалена
проверка на "*number<0
", и вызов strtoul()
заменен для корректности на strtol()
(для меня вообще
загадка -- почему это вдруг strtoul() может возвращать
отрицательные числа?!).
22.04.2003: поговорил со Старостенко, повыяснял, имеется ли в Win32 необходимая функциональность. Результат: да, имеется. Похоже, практически вся. Причем, как утверждает Саша, под CVI отсутствует проблема "реентерабельности" -- там "system event" функционирует как callback, а не как сигнал/прерывание, устраняя тем самым проблему нужности ENTER_CRITICAL/LEAVE_CRITICAL и, соответственно, SIG_{BLOCK,UNBLOCK}.
Доп. замечание: а если сделать-таки режим работы "external select", то проблема работы с сигналами под Win32 просто отпадет.
Краткий анализ того, на какие системные/библиотечные вызовы Unix завязана cxlib:
CheckPending()
им проверяется, какие из наших
дескрипторов готовы. Должно беспроблемно иметь WinSock'овский аналог.
sigio_handler()
при его помощи добывается список
дескрипторов, в которых есть данные и которые надо повычитывать.
WaitForCompletion()
используется как
"зависни пока чего-нибудь не произойдет"; наверняка должно заменяться
эквивалентным форточным вызовом (но только корректно -- чтобы
сигналы при этом работали! Ох, мать вашу...).
sigio_handler()
, и n_write(), одна штука в
SendRequest()
.
07.07.2003: поскольку сейчас сделан режим
"external SELECT", то принято решение, что под Форточками (т.е., именно
WIN32, а не CYGWIN) cxlib работает только в этом режиме. Посему
проблема сигналов отпадает, про CheckPending()
можно не
думать (он в режиме SELECT ничего не делает),
WaitForCompletion()
также отдыхает, посему остается
select() в смысле операции "проверить, готов ли данный дескриптор на
чтение".
22.04.2003: изменения идут в
cx_bigcmsg()
:
CX_MAX_DATASIZE
на CX_ABSOLUTE_MAX_DATASIZE
.
Мелкий прикол -- если результат будет превышать
CX_ABSOLUTE_MAX_DATASIZE
, то возвращается
errno=CERCV2BIG
, хотя реально еще никакого пакета совсем
не RCV'd.
Доп./побочные действия:
GrowRecvBuf()
-- точная копия GrowSendBuf()'а, и все
увеличение rcvbuf делается ею.
sigio_handler()
имеется проверка, не превышает ли
hdr.DataSize полученного пакета некоего максимума; так вот, вставлена
проверка, что если пришедший пакет имеет тип
CXT_BIGCRESULT
, то разрешенный максимум --
CX_ABSOLUTE_MAX_DATASIZE
, и только если иной тип -- то
CX_MAX_DATASIZE
.
Хотя все равно это как-то кривовато, ведь при этом не учитывается ни реально отведенный объем (хотя при реентерабельном realloc'е он будет подстроен), ни то, правильный ли это тип соединения и в правильном ли состоянии.
22.04.2003: проверено на cx-bigc в связке с sim_drv -- работает. Неделей позже проверено уже на реальной цыгановской камере -- тоже работает. Этот комментарий реально написан 05.05.2003 -- результаты известны, устоявшиеся, так что помечаем этот пункт как done.
30.05.2003: внес давно просившееся
"идеологическое" изменение в менеджмент rcvbuf
: теперь в
sigio_handler()
осталась одна-единственная коротенькая
проверка, что
cp->recvbuf->DataSize<=cp->recvbufsize
, в
противном случае -- MarkAsClosed(cp,CERCV2BIG)
.
Идея в том, что для обычных операций отведенного в
AllocateConnection()
объема в CX_MAX_DATASIZE по
определению хватит, а потребное для больших каналов рассчитывается и
отводится при отсылке BIGC_REQUEST.
Таким образом, исправлена давно висевшая шняга, что
sigio_handler()
мог вызывать нереентерабельный
realloc()
.
Заодно исправлен давно имевшийся, но почему-то не проявлявший себя
баг (не проявлявший?), что в AllocateConnection()
сначала
инициализировались cp->{send,recv,last}bufsize
, а
потом, после malloc()
'ов, но перед заполнением остальных
полей, делалось bzero(cp)
(сие есть явно результат
"окрасивливания" отведения -- чтобы делалось сразу malloc(xxxbufsize),
а про bzero() забыл; в BACKUP.curcx.20011018/ бага еще не
было). Естественно, после появления вышеупомянутой проверки в
sigio_handler()
этот недочет проявился. Исправление --
bzero()
перенесено в самое начало.
GetServerSpecParts()
при указании сервера через
$CX_SERVER
имелся SIGSEGV.
07.05.2003: позор мне, вместо
memcpy(hostbuf,spec,len);
там стояло
memcpy(hostbuf,spec,hostbufsize);
. Ну оно и давало
SIGSEGV при обращении за пределы сегмента -- при выходе за конец
$CX_SERVER
...
Это имеет смысл делать с полной унификацией с обычными каналами:
т.е., вызов cx_begin()
, набивка запросами -- например,
cx_bigcreq()
, а затем отправка -- cx_run()
.
(Интересно при этом, что как бы осмысленным становится
cx_subscribe()
для больших каналов; но это просто
видимость.)
При этом cx_begin()
вызывает
CheckCd()
с CT_ANY
вместо
CT_DATA
, отдельно затем проверяя, что
type==CT_DATA||CT_BIGC
, а уж cx_run()
меняется аналогично, но плюс еще в зависимости от type
выбирает код для пакета (CXT_{DATA,BIGC}
) и следующее
состояние (CS_{DATA,BIGC}_SENT
).
Очевидным образом, cx_bigcmsg()
эмулируется как
cx_begin();cx_bigcreq();cx_run();
.
14.06.2003: мысль сюда же: а ведь можно будет, когда появится
поддержка БД, и с database lookups делать так же --
cx_begin()
, cx_db_***()
, cx_run()
.
30.06.2003: Множественные большие каналы
поддерживаются. Именно в варианте
cx_begin();cx_bigcreq();cx_run();
. При этом, естественно,
пришлось выкосить структуру CxConnection.aux.bigc
, а
хранить информацию о запросах аналогично data-fork'ам -- в буфере
CxConnection.fib.bigc
.
Замечание 1: вследствие получившейся
унификации DATA и BIGC надо б слегка перетрясти порядок функций/секций
в cxlib.c -- cx_{begin,run}()
, как ныне
универсальные, вынести в свою секцию, а содержимое "DATA" и "BIGC"
посгруппировывать, одинаковым образом.
Замечание 2: для более полной унификации
следует переобозвать BigPktDataSize()
(весьма misleading
название -- теперь это совсем не "data" size) в BigcForkSize.
Переименуем заодно ForkSize()
в
DataForkSize()
?
03.07.2003: переименовал "ForkSize
"
в "DataForkSize
", "BigPktDataSize
" в
"BigcForkSize
".
cx_begin();{cx_getv*()|cx_setv*()|cx_bigcreq()};cx_run();
.
И запросы к БД позволять наравне с остальным. Кх-м-м-м... А ха-ха
не хо-хо? Ведь тогда будет исключительно муторно делать проверки
корректности -- типа cx_subscribe()
, а то "подпишутся" на
БД...
30.06.2003: Добавил новый код ошибки
CEINVCONN
"Invalid connection type". Он отдается в
cx_{begin,run}()
если соединение и не CT_DATA, и не
CT_BIGC, а также в CheckCd()
, если тип соединения
отличается от запрошенного (раньше это условие было запараллелено с
IsAValidCd()
и отдавало CEBADC; теперь же CEBADC означает
только "неправильный дескриптор" -- аналогично EBADF).
Заодно разобрался, что это за мистический код
CESMALLBUF
"Buffer is too small" -- он был заведен для
cx_db_*()
и означал, что предоставленный клиентом буфер
слишком мал, чтобы вместить ответ. Для большей понятности поменял
текст на "Client-supplied buffer is too small".
Также заодно добавил новый код ошибки
CESYNCINSELECT
"Synchronous connections are forbidden in
select()-mode" -- он будет отдаваться при попытке использовать
синхронные (т.е., не-асинхронные) соединения в режиме работы "SELECT".
Причина -- если запретить не-асинхронные соединения в режиме работы
"external select()", то мы решим проблему deadlock'а, который иначе
обязательно бы там возникал.
CheckSyncReply()
, и если по лени -- то начать им
пользоваться.
30.06.2003: частично понял -- реально оно
вызывалось из async_CS_DB_SENT()
, которого сейчас, ясно,
нету, а в остальные функции так и не вошло. Отчасти -- потому, что в
async_CS_DB_SENT()
код очень похож, но там не "Sync" -- и
Seq там другое.
Что для этого необходимо:
cx_setlibmode(CXLIB_MODE_{SIGIO_{MASTER,HARSHMASTER,SLAVE},SELECT})
.
Режим "SIGIO_HARSHMASTER" -- то же самое, что просто "SIGIO_MASTER", но
рассчитанный на "тупого slave'а" -- с постоянной переустановкой сигнала
после вызова slave'а, на случай, если тот в обработчике переставляет
сигнал на себя.
Эта функция может быть вызвана лишь один раз, и лишь ДО установления соединения.
04.07.2003: константы заведены.
06.07.2003: Функция изготовлена.
cx_setlibslave(pointer-to-handler slavehandler)
-- оно
может использоваться в любом из режимов "SIGIO_*", и будет вызываться
ПОСЛЕ нашей обработки.
06.07.2003: Функция изготовлена.
cx_fd_of(int conn)
-- отдает дескриптор соединения, и
ТОЛЬКО в режиме SELECT.
06.07.2003: Функция изготовлена.
cx_entry_select()
, cx_entry_sigio_slave()
-- точки входа для вызовов снаружи. Разрешается вызывать только в
режимах SELECT и SIGIO_SLAVE, соответственно.
07.07.2003: Функции изготовлены.
sigio_handler()
от обработки
сигнала -- обозвать эту функцию, к примеру, handle_input()
.
Это повлияет и на CheckPending()
.
07.07.2003: Готово. Теперь есть три отдельных
функции: handle_input()
, занимающаяся собственно чтением
данных; sigio_handler()
-- собственно "получатель"
сигнала; perform_onsignal_actions()
, вызываемая из
sigio_handler() и из cx_entry_sigio_slave().
А на CheckPending()
это повлияло лишь в том смысле, что
пришлось там поставить "if(!modeisasync)return;
",
поскольку оно вызывается не только из
perform_onsignal_actions()
, но и из
ConnectTo()
при
OPTION_MUST_USE_NONBLOCK_FOR_ASYNC
-- т.е., под Cygwin.
07.07.2003: сделано.
{ENTER,LEAVE}_CRITICAL()
ничего не делают в режиме
SELECT.
06.07.2003: Сделано -- вставлены проверки
"if(!modeisasync)return
" перед собственно манипуляциями с
сигналами -- т.е., ПОСЛЕ изменения "critical_counter
",
дабы этот счетчик отражал текущую "степень критичности" даже в режиме
SELECT.
07.07.2003: вставил в ConnectTo()
проверку "если (!modeisasync &&
!cp->nonblocking)
, то отдать CESYNCINSELECT".
06.07.2003: поскольку в этих функциях много
"условий", когда в зависимости от режима/CEWRONGUSAGE
"Wrong usage".
07.07.2003: была осознана необходимость отразить все вышеописанные правила в programming manual. Посему некое базовое описание сделано в свежесозданном файле doc/cxlib.txt, в разделе "Режимы работы библиотеки".
07.07.2003: в общем, кажется, все готово. Теперь надо все протестировать.
11.06.2014: за ненадобностью (уже давно, а с переходом в 2012-м на cxscheduler стало совсем неактуально) удалено.
17.07.2003: сделал я ее -- в конечном итоге был взят и слегка облагорожен код из tsycam.c.
Теперь надо ее же заиспользовать и в tsycam.
18.07.2003: сделано -- "заиспользовал".
ERESTART
, который по смыслу явно означает то же
самое, что и EINTR
, но в Linux не используется (кажется,
оно применяется в Solaris). Портабельности ради явно надо везде, где
есть проверка на EINTR
, вставить
"||errno==ERESTART
", естественно, внутри "#if
defined(ERESTART)
".
Может, сделать макрос в misc_macros.h? Есть два варианта -- или макрос
или "wrapper"#if defined(ERESTART) #define SHOULD_RESTART_SYSCALL() (errno == EINTR || errno == ERESTART) #else #define SHOULD_RESTART_SYSCALL() (errno == EINTR) #endif
Второй вариант элегантнее, но сопряжен с "проблемой скобок и запятых в списке параметров макроса" при указании#define DO_UNINTERRUPTIBLE(retval,action) \ do \ { \ action; \ } while(retval < 0 && SHOULD_RESTART_SYSCALL()) \
action
. В любом
случае, явно стоит проверки вида
"if(r<0){if(errno==EINTR)continue;...}
" привести к виду
"do{...}while()
" (хотя это и введет лишнюю отдельную
проверку вида "if(r<0)return -1
", но будет красивее и
читабельнее).
07.07.2003: сделал макрос
misc_macros.h::SHOULD_RESTART_SYSCALL()
и заменил
им все проверки на EINTR
.
07.07.2003: Поскольку раньше в
uclinux_driverbody.c не было #include
misc_macros.h, а теперь есть, то копии
n_{read,write}()
стали более не нужны -- они выкинуты. За
компанию удалил оттуда древний вариант DriverBody()
-- без
поддержки больших каналов, так файл стал вообще коротким и красивым.
И еще -- поскольку мы теперь используем misc_macros.h и под
uClinux, а в uClibc нету vsnprintf()
, то пришлось загнать
check_snprintf()
внутрь "#if !defined(CPU_MC5200)".
ENOTCONN=107
/"Transport endpoint is not connected" вместо
ожидаемого ECONNREFUSED=111
/"Connection refused".
26.10.2003: разбирательство показало, что
проблема -- в том, что TurnToAsync()
выполнялось еще ДО
connect()
, а, по крайней мере в Linux, "Connection
refused" -- это тоже событие, вызывающее SIGIO, так что сразу за
LEAVE_CRITICAL()
тут же вызывался обработчик сигнала,
пытался делать select()
, и втихушку подменял код ошибки.
(Несколько напоминает то, что происходило в древней версии
(oldcx/curcx), где из-за необходимости back-connect'а на лету
подменялся дескриптор, и ядро слегка дурело.)
Очевидным стало решение -- засунуть
TurnToAsync()
ЗА connect()
, и делать
только в случае, если connect()!=-1
.
Ню-ню, сразу две проблемы:
NotificationProc()
, каковая при сразу-возникшем
ECONNREFUSED
, естественно, не вызывается.
TurnToAsync()
делается уже
после завершения connect()
, то сигнал "соединение
произошло" не прилетает. Оппаньки!
Первая проблема решилась путем выноса печати в отдельную функцию
TakeCareOfSuffering()
, которая вызывается из всех трех
мест, имеющих дело с результатом cx_connect()'а/облома --
NotificationProc()
, ReconnectProc()
,
cda_new_server()
.
Вторая решилась просто -- у нас и так вызывалось
CheckPending()
при
OPTION_MUST_USE_NONBLOCK_FOR_ASYNC
, а теперь оно
вызывается всегда.
Итого, этот кусок кода поменялся между версиями
cx.20030828_Knobs и "20031028" следующим образом:
/* Put the socket into asynchronous mode */
AddUsed(s);
r = TurnToAsync(s);
if (r < 0) return -1;
/* And, finally, connect() it */
cp->state = CS_OPEN_ACCESS;
ENTER_CRITICAL();
r = connect(s, serv_addr, addrlen);
#if OPTION_MUST_USE_NONBLOCK_FOR_ASYNC
set_fd_flags(s, O_NONBLOCK, 1);
CheckPending();
#endif
LEAVE_CRITICAL();
if (errno == ENOENT) errno = ECONNREFUSED;
if (r < 0) return -1;
Стало (замечание: AddUsed()
переехал внутрь
критической секции несколькими днями раньше, чтобы случайно (из-за race
condition) не вызывался select()
с еще неконнекченным
сокетом по приходу данных в другой сокет):
/* And, finally, connect() it */
cp->state = CS_OPEN_ACCESS;
ENTER_CRITICAL();
AddUsed(s);
r = connect(s, serv_addr, addrlen);
if (r >= 0)
{
/* Put the socket into asynchronous mode */
r = TurnToAsync(s);
if (r >= 0)
{
#if OPTION_MUST_USE_NONBLOCK_FOR_ASYNC
set_fd_flags(s, O_NONBLOCK, 1);
#endif
CheckPending();
}
}
LEAVE_CRITICAL();
if (errno == ENOENT) errno = ECONNREFUSED;
if (r < 0) return -1;
Общий вывод: практически ВСЕ теперь делается внутри критической секции.
Заодно заметил такой прикол: даже в синхронном
режиме библиотеки (CXLIB_MODE_SELECT
)
TurnToAsync()
все равно вызывалась, хотя обработчик
SIGIO при этом не ставится, что неизбежно должно было приводить к abort
по неперехваченному сигналу. Почему я этого не замечал -- загадка...
Кроме того, в это же изменение протокола стоит заложить и будущую возможность отсылать более одного bigcmsg в одном пакете.
07.04.2003: итак, что реально сделано:
CXT_ZZZ=CXT_access+NN
они переделаны в
формат CXT_ZZZ=NN*0x01010101
. К этим же кодам добавлен и
CXT_EBADRQC
, поскольку он может возвращаться сервером при
странном коде от клиента.
CXS_ENDIANID=0x12345678
, который отсылается в поле
CxHeader.Status
в первом пакете, идущем от клиента к
серверу -- cxlib.c::async_CS_OPEN_ACCESS()
. В будущем это
позволит определять endianness клиента.
cxlib.c::cx_bigcmsg()
вставлено присвоение "1" полю
NumData
. Больше в самом протоколе, похоже, менять ничего
не придется (а флаг "immediate" -- его как, он же должен быть
per-packet, а не per-command? Who-o-o-ps... Наверное, надо будет
сделать в сервере per_packet_flag равным AND'у всех
per_command_flag'ов.
ninfo
и
retbufsize
, переданные клиентом -- т.е. сделать "полную
доставку параметров" из функции cx_bigcmsg()
в реальный ее
исполнитель -- сервер. Это позволит во-первых, повысить надежность --
поскольку будет легитимная уверенность, что сервер не пришлет больше
данных в recvbuf, чем было попрошено, а во-вторых, совсем плотно
приблизит нас к реализации возможности делать "set attributes".
Итого -- 1) надо добавить в CxBigCommand
два поля
(может, выкинуть _reserved
? Тогда размер станет 8
int'ов... Нет, лучше _reserved
оставить, а
cachectl
и immediate
объединить в одно поле
-- flags
). 2) Добавить коды CXT_BIGCATTR
и CXT_BIGCATTRRESULT
-- они будут использовать ту же
CxBigCommand
, но с принудительным
.datasize=0
.
19.06.2003: сделал в cx_proto.h
структуру CxBigCommand_new
, в которой cachectl и immediate
объединены в одно поле flags, добавлены параметры ninfo и retbufsize,
поле nparams рассматривается как пара двухбайтных полей.
03.07.2003: еще одна очевидная мысль: в
wire-структуре CxBigCommand*
не хватает поля "код
команды", которое, хоть пока бессмысленно, но весьма нужно в качестве
доп. меры проверки корректности. А в будущем там будет указываться
именно код операции -- то ли именно CXBC_BIGCREQ, то ли CXBC_SETATTR,
то ли еще что понадобится. Резюме: _reserved
выкинуть,
вместо него сделать Command
;-).
25.05.2005: поскольку все вышеупомянутое реализовано уже почти два года назад, давно пора пометить как "done", что и делаем.
MAX_TAG_T=(1<<(sizeof(tag_t)*8))-1
" и пользоваться
ею вместо дурацкого "255".
14.06.2003: сделано -- именно такой enum.
Эта константа используется теперь в
cx-server_channels.c::FreshFlag()
,
cda.c::DecodeData()
(плюс там же
chaninfo_t.mdctr
стало tag_t
вместо былого
int
).
CxBigCommand
" в "CxBigcFork
", что намного
лучше отражает современную суть этой структуры. Тем более, что функция
определения размера уже давным-давно называется
BigcForkSize()
.
CXT_DATA,CXT_DATARESULT
в CXT_DATA_REQ,CXT_DATA_RPY
.
А вот CXT_BIGC*,CXT_DB*
не стал трогать -- все равно их
дни сочтены.
setmaxmessagesize(int fd, size_t maxsize)
".
19.01.2003: в connlib.c вставлен вызов
SetSendParams()
, которому указывается максимальный размер
пакета и максимальное количество пакетов в буфере (defaults:
size=CX_MAX_PKTSIZE, num=4); при этом размер только растет, но никогда
не уменьшается.
22.04.2003: Вставил в
cx-server_bigc.c::FillBigcReplyPacket()
вызов
SetSendParams()
с надлежащим размером пакета.
22.04.2003: ну проверил. РАБОТАЕТ!!! Я соответствующим образом настроил позавчераизготовленный драйвер sim_drv, и оно отлично пересылает метр данных (500000 short'ов). Для теста пришлось временно увеличить до 1'000'000 MAX_LENGTH в sim_drv.c и MAXRESULTS в cx-bigc.c.
22.04.2003: также симметричные изменения на прием
пакетов: в cx-server_bigc.c::ServeBigcRequest()
datasize проверяется на превышение
CX_ABSOLUTE_MAX_DATASIZE
вместо прежнего
CX_MAX_DATASIZE
; в
connlib.c::GenericReadFromClient()
вставлена
"интеллектуальная" проверка размера, скопированная из
cxlib.c::sigio_handler(). Это (отсылку серверу больших порций данных),
впрочем, пока не проверял.
Можно сделать очень простую оптимизацию: при очередном прошедшем
uintr_write()
в GenericDoClientWReady()
не
двигать данные к началу буфера, а просто увеличивать некий
"_sysbufoffset
", а реально перемещать только при
необходимости что-то добавить в GenericSendToClient()
--
т.е., вместо "do always" будет "do on demand". Хуже от этого точно не
будет -- потерь-то никаких, кроме чуть более сложного кода.
24.04.2003: сделал, именно так; особое внимание
уделено корректности состояния _sysbufoffset -- чтобы он сбрасывался в
0 не только при сдвижке к началу, но и при полной отсылке. Вроде
работает, причем реально в проведенном тесте (большой канал из sim_drv)
memmove()
никогда не делается.
Дополнение 29.04.2003: сегодня при отладке tsycam_drv
наткнулся на оставленный баг: забыл-таки в
GenericDoClientWReady()
адрес для write()
поменять с cp->_sysbuf
на
cp->_sysbuf+cp->_sysbufoffset
. Исправлено.
active_magic
, которая обычно
=MAGIC_NOT_IN_DRIVER=-1,
ENTER_DRIVER(magicid)=>{active_magic=magic;}
и
LEAVE_DRIVER()=>{active_magic=MAGIC_NOT_IN_DRIVER;}
,
magicid==active_magic
(которую тоже нужно сделать
макросом).
24.04.2003: макросы и сама переменная сделаны. Теперь надо начать это использовать.
24.04.2003: это, конечно, сравнительно временное решение -- когда появятся "hw_access_layers", то они будут иметь право производить действия "от имени" драйверов, и тогда простая проверка вида "magicid==active_magic" станет некорректна. С ходу видятся два варианта решения:
layerid
(LAYERID_NOT_IN_LAYER=-1, а b_layer[n] по умолчанию -- 0), и всяким
ReturnXXX будет передаваться комбинация вида
layerid<<16|magicid
.
Драйвер при регистрации декларирует: "я принадлежу такому-то
layerid", оно регистрируется в b_layer[magic], и затем позволяется не
только magicid==active_magic, но и
b_layer[magicid&0xFFFF]==((magicid>>16)&0xFFFF)
15.05.2003: угу, классно -- переговорил с Олегом,
как у него устроен can_drv -- вот у него-то оно и есть настоящий access
module, но маскирующийся под первый из блоков. Посему --
fd_io()
вызывается для одного magicid, а
ReturnChan***()
делает с совсем другим. Так что, прежде
чем начинать использовать {ENTER,LEAVE}_DRIVER()
, надо все
же добавить поддержку access layers.
23.06.2003: вообще-то, если честно, эта фича -- не есть first-priority, так что, пожалуй, начало ее использования я отложу до полной реализации access layers.
Замечание 2: драйверу-то совсем не обязательно декларировать свою принадлежность к layer'у -- у сервера при чтении blklist и загрузке драйвера и так достаточно информации, чтобы пометить драйвер как управляемый таким-то layerid.
05.12.2002: Сделано -- она отправляет message серверу, и тот делает DoDriverLog().
Да генерит она все -- в
drivers/LocalRules.mk стоит строчка, вставляющая в
i_mtrlr_rules.mk правило mtrlr_%_drv.o: i_mtrlr.c
../abstract_camac/a_%.h
.
07.03.2003: Ага, щас --
это-то правило стоИт, а как насчет того, что #include
'ится
самим i_mtrlr.c?!
08.04.2003: был еще мелкий глюк -- там стояло
"INTMFILES=*.elf
" вместо
"INTMFILES+=*.elf
", так что файлы *.o не
стирались. Исправлено.
ABSTRACT_*_FUNC
) макросами с параметрами (список
параметров -- пустой). Это позволит иметь код пристойного вида:
ABSTRACT_*_FUNC() { ... }вместо
ABSTRACT_*_FUNC { ... }сбивающего с толку syntax highlight parser как минимум в FTE (разница -- в наличии скобок, превращающих декларацию в нормальное C-образное объявление функции).
07.03.2003: Готово. Поскольку cpp запрещает использование "макросов с параметрами" без скобок, то он сам указал места, куда надо поставить "()" (правда, вместо того, чтобы человеческим языком сказать что-нить типа "parametrized macro used without parentheses", он вякает невнятное "syntax error before `{'").
(Ведь иначе абстрактные драйверы -- очень даже
платформнозависимые! (Мммм... Они как бы сейчас и так
платформнозависимые, но это не столько misdesign, сколько халтурная
реализация. Реальные проблемы --
))
#define BIGC_MAX_DATASIZE/BIGC_MAX_NINFO
, а интерфейс
найдет, как этим воспользоваться.
07.03.2003: Ой-ё... А ведь Андрей был очень даже прав! И даже более того, в m5200_fd_p():M5200DRV_BIGC стоял такой занятный код (в m5200_big_p() -- аналогично в обратную сторону):
switch (retdataunits) { case 1: memcpy(retdata, retdata, retdatasize); break; case 2: memcpy(retdata, retdata, retdatasize / retdataunits); break; case 4: memcpy(retdata, retdata, retdatasize / retdataunits); break; }Как низко я пал! Как это вообще работало при retdataunits!=1 -- ведь код явно был написан для варианта 1:memcpy,2:memcpy_ntohs,4:memcpy_ntohl, и оставлен в расчете на "сделать memcpy_* потом" (о чем благополучно забыто). Прикол в том, что params/info очень даже memcpy_XtoYl'ятся.
Короче -- исправлено.
13.05.2003: последовательность действий:
#include ABSTRACT_DRIVER_FILE
заменен на хитрый кусок
кода
#undef ABSTRACT_MAX_NINFO #undef ABSTRACT_MAX_RETDATASIZE #include ABSTRACT_DRIVER_FILE #ifndef ABSTRACT_MAX_NINFO #define ABSTRACT_MAX_NINFO 1 #endif #ifndef ABSTRACT_MAX_RETDATASIZE #define ABSTRACT_MAX_RETDATASIZE 4 #endif . . . static int bigc_info_buf [ABSTRACT_MAX_NINFO]; static char bigc_retdata_buf[ABSTRACT_MAX_RETDATASIZE];
*BIGC_FUNC()
определения
заменены наint **info, int *ninfo, void **retdata, size_t *retdatasize
int *info_buf, int *ninfo, void *retdata_buf, size_t *retdatasize
do_bigc()
: в списке передаваемых
A_BIGC_FUNC_NAME()
параметров "info" и "retdata" заменены
на "bigc_info_buf" и "bigc_retdata_buf", а далее стоят присвоения
*info=bigc_info_buf
и *retdata=bigc_retdata_buf
.
*info=args
и
*retdata=data
заменены на memcpy()
.
Готово (не помню когда именно): оно переименовано в cxsd_driver.[ch], в преддверии будущего единого cxd.
fd_io, do_rw, do_big
(назовем его privptr, default=NULL).
Это позволит почти целиком освободить драйверы от какой-либо работы по
мультиплексированию -- точнее, по lookup'у, необходимому для трансляции
magicid в некую внутреннюю структуру-описатель конкретного блока
(запрос от Олега 04.03.2003, хотя формально я эту необходимость
осознал еще 27.02.2003, а Олег очень хорошо подтвердил мою идею,
попросив в точности того же самого).
(int magicid, int bid, int fd, void *privptr[, ...])Замечание: при этом поменяется порядок параметров у этих "методов" (сейчас порядок -- magicid,fd,bid). Введение privptr, который не int, заставит, конечно, gcc выдавать warning, но вообще-то это неприятно.
#define
'ы, которые бы и объявляли список
параметров.
06.03.2003: а вот этот кусок (что одобрил все тот же
Олег) я решил не реализовывать, чтобы программа была четче видна --
т.е., в объявлении функции присутствовали бы явным образом ее
параметры, а не какой-то безликий макрос. Это дает еще один плюс:
можно ненужные параметры тэгировать
__attribute__((unused))
.
CXSRV_DRIVERREC_VERSION
!
06.03.2003: Сделано. Все прошло именно так, как и
предполагалось (а какие проблемы могли быть-то?).
CXSRV_DRIVERREC_VERSION
продвинуто с 1000000 (major=1*e6)
до 2000000 (major=2*e6).
07.03.2003: Ох, как хорошо я лопухнулся в процессе
прикручивания RegisterDriverPtr()
: я решил вставить в
него, а заодно, задним числом, и в RegisterDriverFD()
проверку что если magicid>=numblocks
, то ругнуться.
А ведь в SimulateDatabase()
(когда реально все
драйверы все и регистрят) используется свой счетчик блоков, он
же magic, который лишь в конце копируется в numblocks, а numblocks==0.
В результате всё обламывалось.
Прикол в том, что такая фишка была сделана не зря, а с дальним прицелом: на будущее, чтобы можно было сделать "reload database", и при этом имелось бы 2 штуки объекта "рабочая база данных", дабы при "обломе" загрузки нового варианта оставался бы живым старый. При здравом размышлении, однако, ясно что такое невозможно в принципе: бывают ресурсы, допускающие лишь эксклюзивное использование (типа CAN-адаптера), так что придется вначале долбануть текущую БД/конфигурацию, а потом создавать новую. Единственным выходом видится хитрая инфраструктура типа "замри" (читай -- прикрой свой CAN-дескриптор), но она слишком усложнена и подвержена ошибкам. Так что разумнее будет остановиться на модели обновления БД из двух уровней сложности: 1) "простой" -- изменение лишь разнообразных коэффициентов, позиций блоков, и прочей реинициализации (пусть даже с close()/open()) живых драйверов -- это вообще можно делать "на лету", без остановки системы; 2) полный -- когда "reload" делается как "clear current" с последующим "load".
В общем, временно я эти проверки прибил, поставив к ним
0&
, а алхимию с numblocks трогать пока не стал. Но
вообще-то, похоже, надо в SimulateDatabase()
манипулировать сразу с numblocks (если не придет в голову какой-нибудь
элегантный способ реализовать загрузку новой БД без пришибания старой).
01.03.2004: пришлось-таки пофиксить проблему, и вот почему.
Во всех RegisterDriverNNN()
проверки
magicid<numblocks
были отключены (при помощи
"&&0
"), НО! В ReturnChanNNN()
и в
DisconnectBlock()
оно было очень даже включено. Как
следствие -- из init_b()
сразу вернуть значения каналов
было нельзя!
А в kshd485_drv.c такая необходимость возникла (потом она,
правда, отпала, но было уже поздно ;-), так что теперь
SimulateDatabase()
сразу по мере добавления блоков
модифицирует numblocks
; точнее -- это делает
AddBlock()
сразу после отведения ресурсов для блока и
перед вызовом init_b()
.
Теперь прямая дорога к
удалению "&&0
". Но с этим подождем до
введения "правильных" layer'ов и {ENTER,LEAVE}_DRIVER()
.
24.05.2005: ну нафиг -- чего ждать, взял да
поудалял все эти "&&0
" прямо сегодня.
15.10.2008: замечание на будущее (в CXv4): первая мысль по прочтению предыдущего раздела -- "а ведь нельзя что-то возвращать прямо из добавления блока, т.к. это будет в момент «создания нового объекта списка аппаратуры»!".
Потом подумал лучше -- неа, все можно, т.к. речь про
init_b()
, а он вызывается уже в момент "реализации"
(realize) БД/аппаратуры, т.е., когда она становится живой. Так что --
всё окей.
magicid==0
. Причина --
проблема, изложенная Олегом 04.03.2003: дело в том, что у него делается
lookup по таблице, где ключом является magicid. Поскольку magicid
может быть 0, то ему пришлось в DrvInitFunc забивать таблицу значениями
-1. А если бы имелась гарантия, что magicid!=0, то можно было б
полностью удовлетвориться стандартом C, гарантирующим забивку всех
неинициализированных статических переменных нулями.
05.03.2003: Может, вызывать для magic=0
AddBlock()
со всеми нулевыми размерами? Это эффективно
продвинет magic с 0 на 1, корректно заполнив все lookup-таблицы и не
заняв ни номеров каналов, ни памяти.
06.03.2003: Сделал, именно этим способом. При сим
было узкое место: имеется ведь единственная функция
AddBlock()
, которая делает две вещи: 1) dlopen() драйвера
и нахождение его driverrec'а; 2) собственно заполнение таблиц,
описывающих этот блок. А нам нужно було исключительно #2 -- пришлось
вводить хак, что при drv_name==NULL
генерится
fake-driverrec (весь нулевой), и делается goto в место заполнения
таблиц. Прикол в том, что goto делается ровнехонько в обход кода
загрузки драйвера, нахождения его driverrec'а и проверки корректности
оного. А ведь если бы были ДВЕ отдельные функции -- получения доступа
к driverrec'у (с предварительной, при надобности, загрузкой драйвера) и
заполнения (как хотелось сделать для систем без
dlopen()
!!!) то все прошло бы намного проще.
Вывод: надо делить AddBlock()
надвое, продумав
при этом, в каком месте оставить проверку корректности параметров
(main_count==metric->main_count etc.) -- она-то явно к коду
загрузки/эмуляции загрузки уже не относится.
??.04.2003: изготовил необходимое для
деления AddBlock()
на части -- функции
LoadDriver()
, CheckDriverMetric()
и
CheckBlockInfo()
. И забросил сделанное :-).
20.06.2003: еще перекочевряжил
AddBlock()
-- теперь три вышеупомянутые и давносделанные
функции используются. goto
устранено -- стало довольно
простейшего if-then-else.
Имеет смысл сменить формат "main_count r/w stat_count" на список-через-запятую, с префиксным/постфиксным указанием типа (r/w), например, "r40,w8,r8" (козаковский CAN-ADC). Это решение будет полностью соответствовать будущему указанию больших каналов -- которое тоже предполагается делать через запятую. И, естественно, надо иметь возможность указывать в поле "chaninfo" просто "-" для драйверов без обычных каналов.
Замечание: при таком изменении надо будет еще разок сменить формат
DriverRec'а, ведь поля {main,stat}_count
теперь становятся
бессмысленными (кстати, а где main_type
? Я его забыл!).
Надо будет иметь одно -- видимо, просто main_count
. И при
этом надо обязательно опять сменить
CXSRV_DRIVERREC_VERSION
!
07.04.2003: поле stat_count
из
DriverRec'а удалено, и из DEFINE_DRIVERREC'а тоже. Версия теперь
CXSRV_DRIVERREC_VERSION=3000000
-- опять инкремент на 1.
Несколькими часами позже (после переделки AddBlock): DriverRec еще
раз поменян -- теперь в нем унифицированно указываются пары
main_nsegs::main_info[]
и
bigc_nsegs::bigc_info[]
.
07.04.2003: очень основательно перепахан
cx-server_dbase.c: функции AddBlock()
и
SimulateDatabase()
. Теперь парадигма такая -- каждый
драйвер поддерживает N наборов обычных каналов и M наборов больших
каналов.
N.B.: я очень доволен современной моделью -- все очень красиво и унифицированно, причем совершенно одинаковые имена используются и в DriverRec'е, и в параметрах функций.
Следующий шаг -- делить AddBlock()
на три функции, при
этом сокращать объемы -- там есть дублирующий код в обработке
main/bigc, плюс очень одинаково выглядят все "if (error)
{do_error_message(); return_from_here}". Причем в
SimulateDatabase()
парсинг main_info/bigc_info тоже
порядком совпадает -- как бы их подунифицировать (или итератор,
но это проблем с передачей контекста, или #define; наверное, проще,
хоть и "кривее", последний вариант).
И еще: надо ОБЯЗАТЕЛЬНО переписать src/doc/blklist.txt.
08.04.2003: src/doc/blklist.txt изменен и теперь отражает текущий формат файла.
08.04.2003: Проверить бы все на bug-free, а? Сделана лекция Олегу и Андрею Чугуеву, но энтузиазма пока маловато. С Олегом договорено на завтра. Вот начнут они использовать -- баги и повылазят, если есть...
18.04.2003: в общем, в те следующие несколько дней вся троица наконец перешла на новую версию, и вроде пока глюков не отмечено...
30.10.2003: решение было довольно очевидно: теперь можно просто писать до 4 чисел через запятую, и парсер blklist'а сам произведет должное умножение.
Т.е., запись вида N1,N2,N3,N4 транслируется в
N1+N2*256+N3*65536+N4*16777216
, тем самым производится
"раскладка по байтам".
Доки в docs/blklist.html также поправлены.
30.10.2003: заодно, "на будущее", если
понадобится кодировать два 16-битовых адреса, добавлен разделитель
"точка с запятой" -- он дает сдвиг 16 вместо 8: N1;N2 =>
N1+N2*65536
.
Естественно, это открывает дорогу и хитрой кодировке "N1;N2,N3" -- 16bit,8bit,8bit (а 8bit,8bit,16bit возможно и просто как "N1,N2,N3"); и даже извращению "N1,N2;N3" -- когда N1 и N3 занимают крайние байты, а N2 -- два средних. Важно -- потом, при переходе к настоящей СУБД, не запутаться в этом всем и адекватно отразить.
ShouldProcessChannel()
,
определяющая алгоритм запроса каналов у драйверов. Там было в секцию
ACTION==DRVA_READ
добавлено
дабы, будучи единожды прочитанным, повторно в этом же цикле канал не спрашивался.if (c_time[chan] == current_cycle) return 0;
Причина: у Олега имеется такой занятный драйвер,
который проверяет CurrentCycle()
, и если в этом цикле еще
не выполнялось чтение, то он отправляет запрос на чтение всех каналов
блока оптом (ну блок так устроен, что меряет все сразу; ведь всякие
УРы и СДСы просто по смыслу именно таковы) и по приходу ответа
делает ReturnChanGroup()
. Если же чтение уже было, то он
просто отваливает, считая, что у сервера уже и так все есть, даже не
делая ReturnChan*()
(вот поц!).
Олег, конечно, нехороший человек, что не выполняет вполне четко определенный запрос сервера (ну обязательно надо ответить ведь!), но, с другой стороны, лучше действительно серверу действовать более осмысленно и предсказуемо, и не просить читать тот же канал повторно в одном цикле.
current_cycle=0
на =1
.
Причина: в вышеупомянутом драйвере переменная,
хранящая номер цикла, в котором было выполнено последнее измерение,
инициализировалась 0 (нулем), поэтому, если некая программа умудрялась
послать запрос на чтение в первом же цикле (а таковы все Chl-based
программы, делающие reconnect -- они сразу же ловят свежезапущенный
сервер), то Олег этот запрос игнорировал и канал так и оставался
помеченным c_rd_req[chan]=1
, так что больше и не читался.
Вообще-то с философской точки зрения подход "1 вместо 0" довольно
осмыслен: циклы считаются с 1-го, как и людьми (нас ведь здесь не
колышет проблема 2000/2001 года -- какая нам разница, сколько циклов
прошло с запуска сервера), а значение 0 является "зарезервированным",
"невозможным", etc. -- так что эта схема опять же хорошо ложится на
стандарт C, с забиванием статических переменных и памяти, отведенной
calloc()
'ом нулями.
21.04.2003: определен тип
CxsrvBlkToutProc
и декларирована в
cxsd_driver.[ch] функция RegisterDriverTimeout()
,
которая пока, впрочем, ничего не делает.
23.04.2003: Ну, это, ик... Вроде как сделал --
даже самому не верится. Создан новый модуль cxsd_events.c,
который и содержит функции менеджмента очереди таймаутов. А
Run()
переделана так, что содержит вполне полноценный
scheduler.
Сама идея подсмотрена в Xt (xc/lib/Xt/NextEvent.c): при
добавлении таймаута ему находится место в упорядоченной очереди;
select() делается на время, равное разности между временем срабатывания
первого таймаута в очереди и now; "основной" таймаут (который тактовый
по циклам) приравнен к драйверным, и когда он "щелкает" (конец
очередного такта), то просто сразу из его callback'а устанавливается
следующий. (Меня, кстати, очень позабавило, что уже после того, как я
сделал макрос сравнения timeval'ов TV_IS_LATER()
, в Xt
обнаружился макрос IS_AFTER()
, практически полностью
совпадающий с моим, разве что у них скобок поболее. Так что я даже
"совместимости ради" переименовал свой макрос в
TV_IS_AFTER()
.)
Минус современной "конфигурации" -- вызов таймаутов делается все же
из cx-server.c::Run()
, а не из
cxsd_events.c, так что инкапсуляция не полная. Впрочем, при
переходе к однопрограммной архитектуре все равно понадобилось бы унести
Run()
в отдельный файл, вот как раз и унесем его в
cxsd_events.c.
Побочный эффект, который меня очень удивил: из-за появления собственного планировщика (или из-за конкретно такой его реализации) сервер стал способен более-менее соблюдать заданный ему квант цикла, даже если он мельче, чем 1/HZ (10ms в Linux/x86). Конечно, это делается не слишком-то точно, но все же это лучше, чем былое "точное" соблюдение кванта 20ms при попрошенном 10ms, когда тех же 100Гц можно было добиться, только попросив <10ms, а выше 100Гц забраться было невозможно вовсе.
28.04.2003: Мя-я-я-я-у-у-у-у!!! Ох и
прокололся же я -- оно просто КОМПИЛИРОВАЛОСЬ, но вместо нового
варианта -- new_Run()
вызывался старый --
Run()
, так что
cycle_step=STEP_DATACOLLECTION
, без которой оно выдает
ошибку в CycleCallback()
);
30.04.2003: проверил работающесть таймаутов на примере tsycam_drv. Вроде как все OK, считаем таймауты функционирующими и помечаем этот раздел как done.
11.11.2003: допЭрло -- ведь тесты-то я раньше проводил на другом ядре, не то 2.0, не то 2.2, а у них, возможно, другой планировщик.
CycleCallback()
время конца
следующего цикла считается как gettimeofday()+DATACOLLECTION_TIME,
вместо положенного step_end+DATACOLLECTION_TIME -- т.е. за точку
отсчета берется не должное, а текущее время, которое
может из-за всяких задержек отставать от расчетного (и отстает -- на
10мс, являющиеся квантом планировщика).
11.06.2003: исправлено, очевидным способом -- в качестве начала следующего цикла берется время окончания текущего.
10.05.2003: в cxsd_driver.h вставлены определения для поддержки сигналов -- typedef CxsrvBlkSgnlProc, троица RegisterDriverSignal(), NoticeDriverSignal(), DeregisterDriverSignal().
24.06.2003: возникла противомысль -- а реально ли нам нужны сигналы? С одной стороны, стопроцентно надежно их реализовать невозможно -- в лучшем случае, они будут хоть редко-редко, но откладываться на квант сервера из-за попадания между проверкой и select()'ом. С другой стороны, при реализации в cxlib и cda режима работы "external-select" никакие сигналы реально не будут требоваться -- chain-cx-драйверы для CX-сервера будут работать при помощи общего select()'а, что даст более корректную и "бесшовную" модель.
??.??.2003: (не помню, когда точно, но записано сюда 04.04.2004): окончательно пришел к выводу, что эта инфраструктура "сигналов" вредна и бессмысленна, и ее зародыша надо удалять.
??.??.2003: Часом позже: извёл. Было бы что... Так что помечаем раздел как "done", а идею и начальную реализацию метим тэгом <S>.
05.03.2003: Похоже, что проще это будет сделать в
новой, однопроцессной версии -- там будет одна-единственная функция
exitproc{0,2}
, в которую можно будет вставить этот
unlink(unix_name)
, а то сейчас cx-porter пользуется
стандартной фичей из cxdpartlib'а, которая ничего подобного не
предусматривает.
??.11.2003: (когда точно сделано, не помню, записано сюда 04.04.2004): в daemon/cxsd_fe_cx.c эта фича появилась практически с рождения. В cxd+cx-server ее, естественно, не будет. Со спокойной совестью помечаем раздел как "done".
16.05.2003: по результатам разговора с Олегом расширенные мысли по поводу второй идеи.
Итак, во-первых: еще довольно давно подумывалось о введении
доп. операции в cxlib, в дополнение к
cx_{get,set}{value,vgroup,vset}
--
cx_doreset_at{value,vgroup,vset}
. Оно добавляется в
список возможных команд CxDataFork.Command
и сервер при
получении такой команды делает reset всем драйверам, чьи каналы
упомянуты в таком fork'е. (Коды команд можно выбрать
CXC_RSTVGROUP='RSgr'
, CXC_RSTVSET='RSst'
;
в ForkSize()
приравнять их к *GET* -- т.е., отправляемые
данные отсутствуют, принимаемые -- есть (еще вопрос: а корректно ли
это? Имеет ли смысл возвращать данные на reset?).)
Во-вторых, модификации в интерфейс драйверов: ведь надо иметь
команду "reset driver", правда? Ее можно уже сейчас делать как
term_blk()
с последующим init_blk()
(хотя
тогда мне придется помнить все auxinfo :-). Но лучше подумать о том,
что наши драйверы уже имеют аналог -- драйверы в ядре, точнее --
скорее, char-device-драйверы. И аналогия достаточно полная:
Ядро | CX-сервер |
---|---|
Уровень экземпляра устройства | |
lseek | Неактуально (возвращает ESPIPE -- illegal seek) |
read | do_rw(...,action=DRVA_READ) |
write | do_rw(...,action=DRVA_WRITE) |
readdir | Неактуально |
select/poll | Неактуально |
ioctl | Пока аналога нету |
mmap | Неактуально |
open | init_blk |
flush | Неактуально (что это вообще?) |
release | term_blk |
Уровень драйвера | |
*_interrupt | fd_io |
init_module | init_drv |
cleanup_module | term_drv |
Отсюда очевидная идея: как насчет ввести аналог
ioctl()
-- "некая некатегорированная операция",
только стандартизовать передаваемые ей коды, одним из которых сделать
reset.
Вторая часть вывода: (подтвержденная/попрошенная Олегом)
разделить do_rw()
на do_rd()
и
do_wr()
-- он утверждает, что в его драйверах
do_rw()
всегда является "переключателем", который в
зависимости от action
вызывает отдельные функции чтения
или записи. Впрочем, мне представляется, что стоит все же оставить
единую do_rw()
-- МНЕ так удобнее, причем везде; а
переключатель -- сложность невеликая.
02.06.2003: просто от нечего делать решил
изобразить функцию ResetBlock(int magicid)
(запихнул
ее в cxsd_driver.c как static void).
03.06.2003: довел функцию до "читабельного" вида.
Выводы: 1) понадобился массив b_metric[by magicid]
; 2)
пришлось сразу рядышком изготовить функции InitBlock()
и
TermBlock()
; 3) этой компании явно не место в
cxsd_driver.c (они -- НЕ интерфейс драйверов к серверу, а
средства манипуляции драйверами для сервера), надо изготовить модуль
cxsd_drvmgr.[ch] и поместить их туда; 4) удалить аналог
InitBlock() из cx-server_dbase.c, заменив его вызовом; 5) в
_dbase.c предполагалось выделить код инициализации драйвера из
AddBlock()
-- а не сделать ли в _drvmgr.c еще и
InitDriver()
?
Плюс, естественно, надо хранить все auxinfo для дальнейшей возможности re-init'а.
Еще замечание: ведь в m5200_drv.c уже напрашивалась функция
"DisconnectBlock()
" -- как насчет ее добавить в
_driver.[ch]?
03.06.2003: несколькими часами позже: сделал я cxsd_drvmgr.[ch].
Заодно изготовил cxsd_includes.h, в который потихоньку
вытащу все #include
из cxsd_*.c.
17.06.2003: фича "рестарт драйверов на лету" работает полностью. Для этого потребовалось:
CXDCMD_SATO_CMD,CXDCMD_SCAN_CMD
в
cx-daemon.h.
drvinfobuf{,size,used}
, поля
b_{drvname,auxinfo}_o[]
, и заполняющий их кусочек кода в
cx-server_dbase.c::AddBlock()
ResetBlock()
Замечание: надо бы сделать доп. поле --
b_active[]
, которое в InitBlock()
взводилось
бы в 1 при успешном init_blk, а сбрасывалось бы при
TermBlock()
и будущем DisconnectBlock()
. Это
нужно, чтобы при выполнении ResetBlock() для блоков, застрелившихся
путем DisconnectBlock(), не пытался заново им сделать TermBlock().
20.06.2003: в AddBlock()
заменил код
инициализации блока на вызов InitBlock()
, в котором
содержится копия былого кода. Кстати: в cxsd_drvmgr.c
отсутствовали проверки на MustSimulateHardware
-- вставил.
Также сделан и начал использоваться b_active[]
.
23.06.2003: слегка переделал поведение
AddBlock()
при ошибках загрузки/инициализации
драйвера/блока -- теперь такому блоку присваивается
metric=&stub_driverrec
, что напрочь выводит его из
реальной работы, но занимаемые им каналы сохраняются. Тем самым мы
избегаем сдвига нумерации при ошибках конфигурации/драйвера.
Кроме того, в AddBlock()
теперь
b_doio,b_dobig
прописываются NULL'ами, а реальными
значениями из metric заполняются в InitBlock()
,
соответственно, сбрасываясь обратно в NULL'ы в
TermBlock()
. Сие сделано в подготовке к
DisconnectBlock()
.
Еще изменение: теперь init_drv()
НЕ МОГУТ возвращать
дескриптор -- только 0 при "удачной инициализации" и !=0 при ошибке.
Чуть-чуть позже: ну сделал DisconnectBlock()
. Это
просто переходник к TermBlock()
, с предварительной
проверкой magicid
.
И еще чуть позже: и InitDriver()
тоже сделал. Куце,
конечно -- ведь никакого "TermDriver()" нету, да и reference-counting
(из {Init,Term}Block()'ов) не ведется, но хоть что-то.
02.07.2004: поскольку все это давно работает (уже с год как :-), и вторая альтернатива "проиграла", помечаем раздел как "done".
20.04.2003: сделан sim_drv.
В смысле обычных каналов он полностью повторяет функциональность null_drv.
Данные же, что отдаются в большие каналы, определяются в auxinfo строкой вида U[length[/period[/amplitude[/usecs]]]], где U:b|s|i -- тип данных (bytes/shorts/ints), аналогично bigc_info; length -- количество данных (в единицах типа); period -- периодичность повторения "сигнала"; amplitude -- амплитуда "сигнала"; usecs -- принудительная задержка перед ReturnBigc().
Отдаваемый "сигнал" представляет собой симметричную "пилу" от 0 до
amplitude, достигающую максимума на середине периода, модулированную
значением +random()&7
, с каждой выборкой едущую на
пиксел влево (фаза считается как x+precession
, и с каждой
выборкой precession++
).
Возвращаемая же info,numinfo является копией args,nargs.
Вообще-то, по-хорошему, надо бы сделать некий "умный" sim_drv, который был бы программируем a-la логические вычислимые каналы. Вот только как именно, да и вообще дюже трудоемкий сие проект.
10.05.2003: переименовал
CxsrvDrvFDProc
в CxsrvBlkFDProc
.
d_data[*].{cur_args,cur_info,cur_retdata,sent_args}
,
выделяется в буфере cx-server_data.c::bigbuf
, отводимом в
динамической памяти и все время растущем. Но ведь НЕЛЬЗЯ сохранять
указатели на места внутри realloc'ируемых буферов -- только смещения в
них! Это глюкалово работало лишь потому, что bigbuf рос эксклюзивно,
не перемежаясь с другими буферами, таким образом -- он всегда был в
конце сегмента, и увеличивался не перемещаясь.
Очевидный фикс: переместить bigbuf{,size,used}
в
cx-server_data.h, переделать в bigchaninfo_t
все
вышеупомянутые указатели в смещения, а указатели вычислять
непосредственно перед использованием (сделать макрос --
GET_BIGBUF_PTR(offset,type)=((type*)(bigbuf+offset))
?).
04.06.2003: вроде сделал, именно так. Все xxx*
переделал в int и добавил к именам суффикс "_o". Во всех точках
использования обращения к этим указателям заменены на
GET_BIGBUF_PTR(..._o,...)
.
Run()
сам делал бы запросы на
чтение всех каналов записи -- по крайней мере, при
"-w N
" это абсолютно необходимо, чтобы иметь в кэше
актуальные уставки из блоков.
14.06.2003: есть -- изготовил функцию
cx-server_channels.c::RequestReadOfWriteChannels()
,
которая проходится по всему списку каналов и делает
MarkRangeForIO(,,DRVA_READ,NULL)
всех диапазонов с
c_type[x]!=0
. В ней пришлось временно сбрасывать в 0 флаг
CacheReadsFromWriteChannels
, чтобы запрос не был отброшен
(как имеющий-результат-в-кэше) фильтром
ShouldProcessChannel()
.
И эта функция вызывается из new_Run()
перед
InitCycles()+BeginOfCycle()
.
16.06.2003: О.Слесарев -- мудозвон! Его
дебильные одреночные драйвера не переносят "предвыборку" -- точнее, они
(по крайней мере, odr_dac16x16) не переносят одновременный запрос на
все каналы чтения: попросту взглючивают и отмирают.
Причем, это не единственный глюк -- во-первых, они там сами себе делают
какую-то типа "предвыборку" (по крайней мере,
ReturnChanGroup()
они вызывают сами безо всякой
предвыборки и при полном отсутствии клиентов), а во-вторых -- при
попытке чтения отдельного канала оне отказываются этот канал отдавать,
а вот при запуске программы "magcorr" -- читают, по-видимому, им важен
порядок запросов -- прямо танцы с бубном какие-то!
Результат -- на пульту новую версию сервера запустить пока невозможно, по крайней мере, без отключения предвыборки.
Надо теперь заставить этого халтурщика пофиксить баг!
current_cycle-c_time[n]
, что равно
1-0, т.е. возраст "1" -- совсем мелочь, считающийся "свежим". Плохо!
Надо бы, поскольку они никогда не мерялись, отдавать "бесконечно
старый".
15.06.2003: сразу и сделано -- теперь
первоначальный current_cycle=MAX_TAG_T
, так что после
вычитания из него пресловутого нолика результат получается "очень
старый".
Замечание о влиянии этой фичи на рестарты драйверов/блоков:
при reset'е драйверов поле c_time[]
можно как оставлять
прежним, так и сбрасывать в 0 -- в первом случае клиенты будут видеть
"непрерывность", во втором -- словно сервер перепустили; главное --
в обоих вариантах никогда не обнулять
currentcycle
, дабы не сбивать монотонность "времени". Во
втором варианте также будет разумным запускать предвыборку rw-каналов
-- чтобы клиенты как можно раньше получали бы готовые данные (хотя
реально в таком исключительном случае, как reset, на это можно и
забить).
printf("%s", NULL)
дает строку "(null)".
25.06.2003: переименовал null_drv.c в noop_drv.c, заодно подправил конфиги.
25.07.2003: моя первая реакция -- нефиг. Дело в том, что вообще-то размер этого таймаута определяется именно характеристиками железа (например, характерным временем отклика Одренка, куда входят реактивность собственно Одренка, время срабатывания блока, время прохождения пакетов по Ethernet, плюс реактивность операционки хост-машины (последней можно и пренебречь)). Квант же работы сервера к этому никакого отношения не имеет (конечно, похвально стараться умудряться все делать вовремя, но если железо не успевает, то мы просто будем пихать ему больше запросов, чем оно способно переварить).
Кроме того, "в будущем", когда понятие "квант сервера" станет довольно иррелевантным (т.е., каждый канал|блок|запрошенный-канал будет иметь свое время измерения), для какого-то драйвера попытка получить этот квант сделается совсем малоосмысленной.
Реакция Олега на первый аргумент: он загрузился, после чего пошел в киоск за едой.
Часом позже: контраргумент от Олега: он хочет не уменьшать, а увеличивать таймауты -- например, если цикл -- 1000мс, а таймаут CAN-bus'а -- 200мс, то можно с чистой совестью, дабы не увеличивать без нужды траффик, растянуть таймаут, например, до той же секунды (замечание от меня: не до секунды, конечно -- максимум до 500мс; т.е., если есть некий "железный" таймаут T, и квант сервера B, то поставить условие "if (T<B/2) T:=B/2").
Как бы то ни было, мое отношение весьма скептическое: возможность получения "BaseCycleSize" -- это лишняя сущность, могущая работать во вред (если не относиться к ее использованию крайне осторожно), а плюсы -- весьма расплывчатого качества.
1) А что, если RCVBUF -- должен быть степенью двойки, и надо сменить 327680 на что-нибудь типа 524288, и оно будет слопано и исполнено; 2) Делать сокет асинхронным даже без режима "SIGIO.chanable", и тогда, вследствие необходимости вызвать обработчик сигнала, программа раньше будет получать процессор. 3) Предложение Леши по модификации протокола: что, если камера ждет ответа на каждый пакет (вар.: на каждые 100 пакетов; вар.2: на каждые N пакетов, N указывается в запросе из соображений RCVBUF/scanline_size), тогда камера не будет переполнять входной буфер.
05.05.2003: попробовал пункт 1 -- RCVBUF. Нифига! Осталось то же 262142. Заинтересовался, полез в ядро, разобрался. Оно регулируется в /proc/sys/net/core/rmem_max, которое домножается на 2, а по умолчанию равно 131071, отсюда и 262142. А в 2.2 (linac) макс. буфер получался 131070 оттого, что там этот sysctl-параметр -- 65535. Надо для проверки, что сканлинии теряются из-за малого буфера, выставить сие значение в что-нибудь типа мегабайта и посмотреть.
Идея, почему бы это могло работать: а что, если на raw sockets
Резюме: поизучать raw sockets, и попробовать использовать их для вычитывания камеры, или хотя бы ловить пакеты в параллель с tsycamlib, и сравнивать потери (а ведь правда, ethereal-то ничего не теряет!).
Идея: если это получится, добавить в tsycamlib вторую возможность -- работа через raw sockets. Для этого программе нужны или права root, или CAP_NET_RAW.
Отсюда -- идея 2: а что, если вставить в будущий
cxsd доп. ключик -- "-p CMD{;CMD}
": выполнить
(где-нибудь при старте) указанную цепочку команд смены приоритетов.
Команды включают то, что должен в пределе уметь runas --
setuid(), chroot(), capset(), etc. Причем цепочка выполняется в
указанной последовательности, так что можно сделать
"capset(=CAP_NET_RAW); setuid(op_server)" (кстати, а как насчет
аналогичной возможности -- указания порядка -- для
runas?). При этом ключ "-u" становится частным случаем
"-p".
??.03.2003: а хрен его знает --
поговорил я с Ильей, ничего он не может вспомнить... Единственное воспоминание -- похоже, предполагалось иметь демон под uid!=oper, слушающий запросы на некоем порту.Было заодно также и обсуждение с Ильей и Лешей, а как же СЛЕДУЕТ сделать. Примерная схема (sorry, она все же моя, так почти и не претерпела изменений за обсуждение):
Итак: если бы все было на локальной машине, то есть некая программа "центр управления", которая по командам пользователя запускает нужные процессы (хоть сервера, хоть программы управления), следит за их статусом при помощиwait()/SIGCHLD
, при надобности прибивает их при помощиkill
. Но ведь у нас-то система сетевая -- процессы временами нужно запускать на других машинах (единственное "исключение": следить за X-клиентами надо вроде как только на своем дисплее, как следствие того, что запускать их надо только на свой дисплей).Первое же решение, которое приходит в голову -- сделать некий "remote execution agent", который бы позволял выполнять
exec()/wait()/kill()
удаленно. И тут же начинаются вопросы:Промежуточный результат обсуждения: похоже, именно такую схему с "центром управления", имеющим возможность как локального, так и удаленного запуска программ, и следует принять (а какие тут вообще могут быть альтернативы?); а вот как именно делать удаленную работу -- пока некий вопрос.
- Как коннектиться к удаленной машине -- по ssh? Хорошо, это настраиваемо (беспарольный вход). Но ведь при этом для каждой удаленной программы на локальной машине будет висеть лишний процесс ssh (который еще и корректно запустить надо); а еще ssh имеет дурную привычку несколько задумываться для вычисления ключей, а в данном случае все это шифрование -- совершенно излишняя работа.
- А не сделать ли так, чтобы на каждой удаленной машине был запущен некий "супервизор", к которому "центр управления" коннектится и передает команды. Этим убиваются несколько зайцев:
Но есть и большие минусы такого подхода:
- не требуются многочисленные копии ssh на локальной машине (да и сам процесс "запусти отслеживаемую программу на удаленной машине" становится существенно проще);
- не требуются многочисленные копии "удаленного агента" на удаленной машине;
- есть возможность иметь в "супервизоре" централизованно общее состояние дел на удаленной машине -- если она обслуживает "центры управления" с нескольких машин, то можно будет легко отслеживать попытки разных "центров" запустить один и тот же cx-сервер несколько раз.
- можно пользоваться таким же "супервизором" и на локальной машине, а самому центру никаких впрямую exec()/signal()/wait() не делать
Побочная мысль, частично как аргумент "против": а можно ведь и для локального запуска пользоваться именно "удаленным агентом", для унификации; контраргумент на "аргумент против": а ведь сам-то "удаленный агент" тоже придется запускать, и, возможно, следить за ним (хотя последнее не обязательно -- можно ограничиться отслеживанием его файлового дескриптора; это даст достаточную гарантию (ситуацию, когда какой-то урод сделает ему
kill -STOP
, можно не рассматривать)).
- Сам "супервизор" должен быть запущен, а если он не запущен -- то не работает ничего (или "центры" должны обладать способностью принять fallback-решение и запустить его по ssh; но тогда сразу есть подпроблема -- что "супервизор" придется гонять под тем же пользователем "oper", иначе будет дыра в безопасности).
Вообще, проблема с тем, пок какими uid'ами пускать сервера, все равно остается; идеального решения пока не видно. Формулировка проблемы: в идеале сервера должны крутиться под неким специальным uid'ом (например, "cx"), чтобы у оператора не было прав их "пришибить"; но, с другой стороны, у оператора должна быть возможность запустить сервер, если он отсутствует/сдох/сдурел.
- Сам мультиплексирующий "супервизор" получится довольно сложной программой (де-факто там будет некое подобие многопроцессности, пусть и на уровне select()/signal()/wait()), как следствие -- подверженной ошибкам.
Как бы то ни было, имеет смысл начать делать этот "агент удаленного запуска" -- во-первых, отработка технологии, а во-вторых -- он такой мелкий, что может быть легко сделан без ошибок.
26.03.2003: ну посмотрел я
/etc/rc.d/init.d/httpd -- нифига там нету умного, там стоит
обычный start(){daemon $httpd ...}
и
stop(){killproc $httpd}
. И там есть ссылочка на
скрипт apachectl=/usr/sbin/apachectl
, который вроде как
поумнее -- вот его-то и надо посмотреть.
15.05.2003: посмотрел /usr/sbin/apachectl. Они всемерно доверяют pid-файлу: если его нету, то Apache считается "not running"; если есть -- то RUNNING=<kill -0 $PID is successful>
Проверка конфигурации (парсинг без реального запуска) делается
ключом "-t
" (аналог нашего "-n
"). Есть аналог
нашего "-d
" -- ключ "-X
" ("Run in
single-process mode, ...; the daemon does not detach from the
terminal...").
Поддерживается "graceful restart" (config re-read?) при помощи SIGUSR1 (у нас аналогичная функциональность будет обеспечиваться через cx-console -- команды stop/reload_database/run).
"Статус-репорт" сервера добывается при помощи "lynx
http://localhost/server-status
".
Резюме: нифига практически нам это не поможет, разве что дает уверенность -- у других не лучше :-).
18.03.2003: идеи от Ильи: при наведении на "область" выплывает tooltip с комментарием -- что это; при single-click -- навигация (XmProcessTraversal(..,CURRENT)), double-click -- открытие окна; right-click -- более подробное, чем tooltip, описание.
wait()/SIGCHLD
и kill()
. Заодно обучимся
всем тонкостям wait*()
.
18.03.2003: минимально сделана (programs/runner/runas.c, инсталлируется в cx/sbin/), по крайней мере, там есть вся инфраструктура -- парсинг командной строки и тела (частично пустые) всех функций. Работают ключи -u,-g,-G -- полностью.
Замечание на будущее: есть некая проблема -- по умолчанию менять
capabilities может только root, а если делать capset()
и
потом setuid()
, то при смене uid'а ядро распотрошит и
capabilities. Т.е., проблема курицы и яйца: что делать раньше --
capset()
или setuid()
-- в обоих случаях
проблемы. Возможное решение: в файле include/linux/prctl.h
есть интересная опция PR_SET_KEEPCAPS
-- "whether or not
to drop capabilities on setuid() away from uid 0".
Еще мелкое замечание: если порядок исполнения команд runas'ом не
устраивает пользователя, то ничто не мешает каскадному вызову:
runas КОМАНДЫ cx/sbin/runas КОМАНДЫ ПРОГРАММА
(надеюсь,
что ничего -- exec()
тоже не должен, хотя существует
такая вещь как close-on-exec, а вдруг и насчет привилегий подобное есть
(стоит вспомнить issetugid(2)
в OpenBSD)).
01.04.2003: Хе-хе, есть такая утилитка
/usr/sbin/sucap из пакета libcap (который и содержит все эти
вызовы). Так вот, она отфорковывает child'а, который не делает
setuid(), сама делает setuid(), а потом командует child'у (который
по-прежнему привилегированный) сделать ей capsetp()
.
10.04.2003: сделано, src/doc/serverspec.txt.
sep[x]=(byte>glt;x)&1
нечто типа
sep[x]=byte&(1<<x)
, что давало "битовые"
значения типа 4, 8, etc. (во хорошо на такие "on/off" что-то
умножать!). Аргументация этого ежика -- типа так код короче.
Вывод: надо срочно делать файл с описанием того, какие единицы драйверы должны отдавать в каких случаях (сюда же присовокупить и "всегда микровольты", и протокол "запись 1 -- пуск, отдача 2 -- disable").
03.05.2003: файл src/doc/chanunits.txt создан, в нем уже описано, КАК положено делать булевские каналы.
cat some-other-.mk Rules.mk
>InstRules.mk
?)
16.05.2003: сделана поддержка внешних программ для programs/chlclients/ -- в дополнение к ранее сделанному вынесению всех определений LIBZZZZ в Rules.mk подчикан еще и programs/chlclients/Makefile. Исправления свелись к
SRCDIR=../..
только если SRCDIR=="".
MKCLIENT=mkclient.sh
на
MKCLIENT=$(MKCLIENT_PATH)/mkclient.sh
, где
MKCLIENT_PATH=.
при вызове из дерева и
MKCLIENT_PATH=$(SRCDIR)/programs/chlclients
при вызове извне.
Теперь извне дерева в "сторонней" директории, содержащей chlclients, должен быть Makefile вида
и, естественно, должны иметься поддиректория DB/ и список SimpleClients.lst.SRCDIR=<some-path>/cx/src include $(SRCDIR)/programs/chlclients/Makefile
18.05.2003: общность: в каждом подобном Makefile должна быть секция ""
19.05.2003: сделал п.в.п. для uclinux_slaves/ -- ох и пришлось же повозиться -- достаточно сравнить этот Makefile (и drivers/Makefile, и drivers/AbstractCamacRules.mk -- в который переименован бывший drivers/LocalRules.mk) с предыдущей версией из BACKUP/...
20.05.2003: насмотревшись на получившийся ужас, пришел к выводу, что надо сменить архитектуру: внутридеревные Makefile должны быть маленькими, и нести только специфику содержимого этой директории, а правила надлежит переместить в LocalRules.mk.
23.05.2003: сделано, для chlclients/, server/drivers/, server/drivers/uclinux_slaves/. Теперь, конечно, некоторая фрагментация произведена, но сколько труда мне стоило сделать эти LocalRules.mk!
Сразу замечание: как минимум, в uclinux_slaves/ есть проблема: там все настроено на очень узкую специализацию -- что только непосредственно абстрактные драйверы можно делать отдельно, а интерфейсы -- только в дереве.
Вообще, кстати, это часть более общего вопроса: надо "важные", требующиеся для "внедеревной" сборки, компоненты делать exports в отдельную директорию (поддерево?), чтобы это а) было унифицировано, б) давало возможность собирать "вне дерева" в варианте после "make install", в отсутствие дерева вообще.
16.10.2003: сделано также для programs/xmclients.
LIBZZZZ=...
куда-нибудь наверх, например, в Rules.mk, и в
зависимости от флага OUTOFTREE делать эти определения или
=$(SRCDIR)/lib/libDIR/libLIB.a
, или
$(TOPEXPORTSDIR)/lib/libLIB.a
, или даже вообще
какое-нибудь $(INSTALL_lib_DIR)/libLIB.a
-- для уже
инсталлированного дерева.
09.05.2003: сделал, в Rules.mk, пока при
помощи "функций" -- $(call FUNCNAME,...)
. Там стоит
определение
а определенияifeq "$(OUTOFTREE)" "" LIB_PREFIX_F= $(SRCDIR)/lib/$(1)/lib$(2).a else LIB_PREFIX_F= $(TOPEXPORTSDIR)/lib/lib$(2).a endif
LIBZZZZ=...
имеют вид
хотя в будущем хорошо бы избавиться от $(call...) -- оно поддерживается только с GNU make>= 78 (у нас используется еще конструкцияLIBZZZZ:= $(call LIB_PREFIX_F,DIR,LIB)
$(if...)
, но от нее вполне можно избавиться.
13.01.2003: надо всунуть туда
set_fd_flags()
, а то оно используется уже в tsycamlib и в
m5200_drv (будет -- в fdiolib; а пока в connlib свой код на
пол-экрана...).
11.04.2003: угу, а сейчас пришлось эту функцию и в cxlib всунуть -- поскольку из-за дурацкого Cygwin'а приходится дергать O_NONBLOCK вниз/вверх при каждом SendRequest().
11.05.2003: и check_fd_state()
туда
же -- а то оно теперь не только в m5200_drv.c, но и в
staromakh.c (хотя нужно ли оно там, ведь там и так
NONBLOCK...?).
04.06.2003: и туда же отправить GrowBuf()
,
CONSUME_BUF()
, GET_INBUF_PTR()
.
26.06.2003: и очень хорошим претендентом выглядит logging-код, содержащийся сейчас в cxdlib.c. Дело в том, что при отладке staromakh было очень неудобно отсутствие нормальной системы логгинга -- чтобы даты писала, чтобы можно было разные события гнать в разные файлы (сделал вывод одной части в stderr, а другой -- в stdout, так при попытке средиректить их обоих сразу и в файл, и на консоль -- что при помощи tee, что >/dev/tty >/var/log/... 2>&1 -- оно глючит с буферизацией). А когда заменим трехпроцессную архитектуру на один cxsd -- вот будет в самый раз.
12.01.2009: во-первых, logging-код давно уже назначен в свою отдельную библиотеку cxlogger.
А во-вторых -- "set_fd_flags()" и "check_fd_state", по нынешнему опыту/состоянию дел, надо просто перенести непосредственно в misc_macros.h.
22.01.2009: всё реально требующееся из заявленного реализовано (кроме CONSUME_BUF()/GET_INBUF_PTR(), которые пока нигде, кроме cx-server_data.h, не нужны) -- см. bigfile-0001.html libs: misc: 15-01-2009. Так что помечаем раздел как "done".
20.04.2003: решение (общее для случая
"поддиректория без своего Makefile") тривиально: в ближайшей директории
c Makefile вставляется строчка вида
"BCKPFILES+=subdir/*\~
". В данном случае это
programs/server/drivers/LocalRules.mk::BCKPFILES+=abstract_camac/*\~
и
include/Makefile::BCKPFILES+=sysdeps/*\~ pixmaps/*\~
Таким образом, можно будет просто скопировать всю эту директорию под Форточки, сказать там "build", и все готово.for %%i in (*.win32.mk) do make -f %%i
24.07.2003: директория внесена в EXPORTSTREE, exports-ссылки на нее вставлены в {include,lib/{cxlib,cda}}/Makefile. Файлы .win32.mk пока отсутствуют.
22.01.2009: этот пункт прикрывается; за отсутствием здесь статуса "withdrawn" ставим просто "done". Подробно описано в bigfile-0001.html за сегодня.
MC5200
" на "MCF5200
" -- все ж таки "MCF"
означает "Motorola ColdFire", и это есть верное название процессора, в
то время как "MC" является ссылкой на "Mamkin's Controller".
17.07.2003: сделано, заменил.
24.06.2003: попробовали XFree86-4.3 с последними на тот момент драйверами от Matrox. Результат -- шокирующий: мало того, что оно вполне работает (для очухивания первых голов non-primary карточек (после переключения в консоль и обратно) достаточно нажать Ctrl+Alt+[+],Ctrl+Alt+[-]), оказывается, этот способ прекрасно работал с первой же конфигурации, поддерживавшей G450PCI. Самое старое, попробованное нами -- XFree86-4.0.3 (RH71) + MatroxDrivers-1.3. Подробности -- в mga_g450.html. Стыд нам и позор...
24.10.2002: вопрос послан, ответы:
Jens Owen: This architectural decision was made many years ago. I don't know if any documentation exists. However, from an architectural perspective it makes perfect sense. The XLib client side library tries to stay thin where possible and defer all heavy lifting to the server side of the implementation.
Jim Gettys: There are many clients, one server. Particularly in the days before shared libraries, this meant that by doing most byte swapping in the server, we ended up using less memory over all. And we anticipated there being multiple implementations of X protocol libraries: this has been less common than we expected, though there is now a very interesting X library under development by Bart Massey and a student.
09.12.2002: ага, щас -- один вечер, ну-ну. Начал делать polytonic -- ох и нудное же занятие!
04.11.2002: ну сделал я его, проверил -- работает, только окошко получается шибко широкое -- нужно разрешение 1600x1200 (или хотя бы 1280xHHH), иначе в экран не лезет. Теперь бы еще опубликовать эту радость...
09.12.2002: нарисовал, вот он:
CXT_{LOGIN,OPENBIGC,CONSOLE_LOGIN} =
byte+byte<<8+byte<<16+byte<<24
; похоже,
надо менять протокол -- чтобы клиент первым присылал пакет, так
что сервер сможет определить его byte order. Хотя сейчас проще сделать
все коды пакетов, которые могут придти ДО первого пакета от клиента,
вида byte*0x01010101
-- это дает код, не имеющий одно и то
же значение при разных endianness. А поле "размер пакета"? При этих
кодах полагать его равным sizeof(CxHeader). Блин, криво-то
как!
11.03.2003: вторая, краткая версия:
29.11.2002: сделано -- cx/src/doc/misc/miscinfo.txt, там же описана аналогия CX и X11.
29.11.2002: уже предоставляется. Для нормальных драйверов это функция GetCurrentCycle(), для абстрактных -- макрос CURRENT_CYCLE() (значение для него передается в каждом пакете запроса, посему макрос отдает не реальный текущий цикл, а цикл, на котором пришел последний запрос; реально абстрактным драйверам только это и надо). Зачем нужно: это позволяет реализовать алгоритм работы "меряем все каналы по первому запросу в цикле, по остальным запросам в этом же цикле -- отдаем уже намерянные значения".
dlopen()
. При этом должна быть возможность
собрать cxd с указанным набором драйверов, ссылки на которые собираются
в специальную таблицу, и затем "эмулятор" dlopen()/dlsym() вычитывает
из нее информацию, не делая никакой динамической подгрузки. (Для
таких платформ все равно останется одна проблема --
malloc()
на них обычно очень лимитированный, но эта
проблема всегда имеет готовые решения.)
??.??.2003: (записано 25.06.2003): сейчас
наличествует функция cxsd_loader.c::LoadModule()
,
которую вполне можно переделать под эмуляцию dlopen() -- на ее
интерфейс это никак не повлияет.
28.10.2002: Опрошены все (Паша, Старостенко, Гуров). Все горячо за. Высказаны идеи: 1) в 08:00/20:00 система сама запускается, требуя дежурного заполнить очередной отчет; 2) сами программы управления периодически сбрасывают туда состояние (вариант: раз в N минут запускается "logger", опрашивающий состояние всех подсистем и сбрасывающий их в logbook); 2.1) частота сброса зависит от частоты изменения (сильно меняется => чаще сбрасывает).
Еще в струю препринта: e-logbook, сканер для ручных графиков. Плюс к этому: как в идеале, а что мешает (бардачность юзеров, ломают принтер, сканер; сканер надо бы под Unix). А не надо ли kiosk-like machine -- print/scan/...?
dlsym()
есть еще dlvsym()
; кроме
того, существует dladdr()
, также нигде в Linux не
описанный, но вполне стандартный (море man-страниц по разным
Solaris/QNX в сети).
canonicalize_file_name()
. Она заодно
резолвит и символьные линки (что уже не есть good).
Кроме того, есть более стандартная/портабельная функция
realpath()
, делающая практически то же самое, но с
некоторыми оговорками.
An information extraction activity whose goal is to discover hidden facts contained in databases. Using a combination of machine learning, statistical analysis, modeling techniques and database technology, data mining finds patterns and subtle relationships in data and infers rules that allow the prediction of future results. Typical applications include market segmentation, customer profiling, fraud detection, evaluation of retail promotions, and credit risk analysis.
24-10-2002: вопрос отправлен. Ответ:
В твоем предложении две системы пользуются одним кабелем. Можно разделять не только кабель, но и блоки, функции.
Возможны различные варианты. Например, одна машина управляет, а остальные (кому это надо) подслушивают. Информация сырая, но ее, пожалуй, проще пересчитать, чем передавать по межмашинной связи (высокоуровневые протоколы съедают много времени). Вариант, все машины имеют равноправное подключение к CAN магистрали. Захотелось с какой-то консоли переключить какой-то АЦП на режим "картинки"- пожалуйста, остальные тут-же об этом узнают и не будут мешаться. Здесь труднее устроить конфликт, чем в КАМАКе.
Ну и т.д. и т.п.
??.02.2003: подумал, поспрашивал Старостенку, ... Результат: решение довольно простое, надо лишь знать "квант", и если прочитанное значение отличается от выставленного пользователем не более чем на квант, то оставлять пользовательское значение. (вариант -- на квант*1.1 (коэффициент надо подгонять)).
Очевидная проблема -- надо иметь возможность получить этот квант из драйвера (но это проблема чисто техническая -- доработать протокол).
Вторая проблема -- что делать с этим квантом для а) LOGK_DIRECT (домножать на коэффициент r?); б) для LOGK_CALCED -- вообще фиг знает...
14.05.2003: полурешение для кванта: сделать в
physprops_t
еще одно поле -- quant
.
(Кстати: а physprops_t.r
-- это ли уже не
тот самый квант для каналов, которые ничего не округляют -- т.е., НЕ
микровольты? Неа, нифига -- у нас-то проблема именно с ЦАПами...)
04.06.2003: кхе, а ведь 1.1-то нельзя делать, и вообще -- должно быть <1, иначе прикол с чекбоксами -- они-то как раз имеют значения Квант*{0,1}.
Собственно, к чему я это -- начато программирование "невырывающести"
ручек. Добавлено поле logchanbin_t.q
, а для проведения
сравнения уставленного и считанного -- пара полей
logchanbin_t.{userval,userval_valid}
, и в
SetControlValue()
вставлен кусочек, выполняющий проверку
(помечен как /*"Quant" wizardry...*/).
На более низком уровне -- добавлено поле physprops_t.q
,
его вычитывание в cda (оно делится на поле r
), и интерфейс
в cda для получения -- cda_getphyschan_q()
.
06.06.2003: проверено с помощью Олега -- работает. Правда, в какой-то момент проверка, кажется, не сработала -- но не уверен, надо набрать статистику побольше.
Теперь задача: везде, во всех DB/db_*.h попроставлять эти кванты для всех интересующих нас каналов.
Кроме того, имеется реальная, теперь уже совсем нерешаемая
проблема: при перезапуске программы, а также если ручку покрутил
кто-то другой, и изменения вышли за пределы кванта, то этот механизм
перестает работать, и на экране появляется реально дробное число
(пресловутое 1.998 вместо 2.0). В рамках нашей модели с этим уже
ничего нельзя поделать (а можно ли вообще в рамках хоть какой-либо
модели -- ведь проблема уже "идейная" -- откуда программе-то знать, что
"1.998" -- это для человека просто "2.0"? Может, нас спасет
dpyfmt
?).
Кстати, мысль: а ведь для многих девайсов, которые наверх отправляют именно код, никак не масштабированный в микровольты, квантом является именно "r" -- может, рассматривать квант "0" как "1.0"? Тогда, по крайней мере, для Олега, все заработает автоматом...
08.06.2003: насчет того, что считать квантом,
0=>1, etc. По здравому размышлению (включающему корректное
детектирование "чужих изменений") стало ясно, что внутри cda квант
должен быть int'ом, без деления на "r". В такой ситуации решение
считать его по умолчанию равным "1" стало еще очевиднее. Сделал,
работает; в cda деление на "r" производится непосредственно в
cda_getphyschan_q()
. Если хочется отключить логику
квантования и действительно сделать квант=0, то в physprops его надо
указать <0 -- например, -1
.
08.06.2003: совершенно случайно при отладке
предыдущего пункта обнаружил, почему проверка иногда не срабатывала:
это происходило в LOGD_TEXT, когда после ввода числа [Enter] уже нажат,
но обновление текстового поля еще не восстановилось (т.е., число еще не
успело уйти в сервер и вернуться обратно), а мышь уводится из окна --
при этом _text_widget.c::ChangeCB() делает "cancel" --
SetControlValue(bi,bi->curv,0)
, при котором,
естественно, вся эта логика "кванта" обходится. Разумное решение:
делать Cancel, только если были реальные изменения -- т.е.,
bi->usertime!=0
. И, по хорошему, надо бы вместо
махинаций с usertime
в _text_widget.c изготовить
в _internals.c интерфейс для этой деятельности --
UserBeganInputEditing()
и CancelInputEditing()
.
Получасом позже: так и сделал -- проблема исчезла.
10.04.2007: ну и лоханулся ж я -- в
cda_getphyschan_q()
с тех давних пор стояло
((double)ci->phys_q) / ci->phys_r
а про то, что коэффициент r может быть и отрицательным -- забыл. В
результате получался отрицательный квант, в сравнении с которым в Cdr'е ЛЮБАЯ
дельта оказывалась больше. Наткнулся на этот эффект Малютин, с каналом
"Spectrometer_set" в linmag'е.
Короче -- от вышеприведенного выражения теперь еще делается
fabs()
.
20.03.2009: сегодня Паша Логачев озвучил проблему: иногда в магнитной системе при изменении стрелками "невырывающесть" ручек все-таки не срабатывает, и вместо нужного, "кратного" значения, появляется именно то, что в железке. И потом уже К НЕМУ прибавляется/вычитается шаг. Например, вместо 700 будет 699...
После некоторых экспериментов выяснилось, что подобное случается при ЗАЖАТИИ стрелок -- т.е., когда куча изменений следуют друг за дружкой, с частотой 30-50Гц.
Ну и что бы там могло при этом глючить?
Возможно, дело в том, что в обоих экземплярах
SetControlValue()
стоит сравнение с q * 0.99
(это и в 4cx/.../datatree.c имеется). А что тогда нужно
ставить -- 0.999999? И откуда и когда взялось это 0.99?
23.03.2009: после легких игрищ нашлось конкретное число-виновник (и СТРЕЛКИ оказались ни при чем). Итак:
В общем -- смена 0.99 на 0.999999 не поможет (1900-1899.435=0.565, ==q, а еще ведь округление), тут прикол в другом. А в чем точно -- надо подсчитывать...
(А не в кванте 305 ли дело -- который сам не вполне точен? Ведь реально-то он равен не 305uV, а 305.1759...)
23.03.2009: провел разборки -- да, ПРОБЛЕМА В КВАНТЕ 305. Итак (детальные расчеты закомментированы в этом файле полстраницей выше), цепочка преобразований при попытке уставить -1900mA следующая:
(И, кстати, при вводе -1900 и OTHEROP в cda также выставляется -- поле оранжевеет.)
Вот если бы учитывался ИСТИННЫЙ квант 305.1759, то было бы q=0.565140, и тогда всё бы работало корректно -- 0.565<0.565140, и даже при q*0.999999 -- 0.565<0.565139.
Вывод: наша схема с квантами в принципе верна, но она НЕ работает с дробными квантами...
P.S. Но 0.99 я все равно на 0.999999 везде сменил.
22.06.2009: первый раз попробовал использовать
q<0
, и вылезла проблема: канал ВСЕГДА светится
оранжевым.
Причина-то проста -- в DecodeData()
стоит сравнение
abs(ci->usercode - ci->physraw) >= ci->phys_q
вот это "=" и срабатывало при
"physprops_t->q<0, =>phys_q=0".
Сменил сравнение на более "умное"
delta != 0 && delta >= ci->phys_q
-- всё стало окей.
(Вначале пытался дурить с двумя отдельными проверками в
зависимости от q --
(q > 0 && delta >= q) || (delta > q)
,
но потом поумнел.)
22.06.2009: кстати, вывел эмпирическое правило: q=-1 надо ставить для каналов, у которых экранная цена деления не-степень-десятки. Например, каналы frolov_bfp: квант и r там 2.5мс, на экране %.1fмс; так что реально-возможные значения 0.0, 2.5, 5.0, 7.5, ..., а юзер может ввести и 1, и 2.7 -- вот чтобы они не остались, а ОКРУГЛИЛИСЬ до правильных, и надо ставить q=-1.
Иными словами: в таких случаях "вырывающесть из рук" уже неактуальна, а нужно наоборот именно "первоначальное" поведение -- с округлением.
02.04.2014@Снежинск-Снежинка-504-вечер-после-ужина: к вопросу о "проблеме в кванте 305":
Конкретно: в случаях, когда драйвер отправил запрос на запись и потом получает результат вычитывания, то в этих редких случаях (когда переход через границу кванта) возвращать не реально записанное в устройство значение, а то, что попросила записать программа.
Т.е.:
После чего в любом случае сбрасывать ch_wrq[]=0.
23.06.2003: первые работы по access layers.
В cxsd_driver.h добавлена секция "Layers" -- все, касающееся
CxsrvLayerRec
.
24.06.2003: итак, первая версия готова. Highlights:
layerinfo
driverrec'а -- последний параметр макроса
DEFINE_DRIVER_REC()
.
layerinfo
мы не можем, а загрузить драйвер можно, только подгрузив нужный ему
layer, то мы получаем "проблему курицы и яйца". Для ее решения
требуемый layer должен указываться в blklist-файле -- через "@" после
имени драйвера; например, "can_dac16@can_marathon".
NAME_l_
.
LAYERSOURCES
-- аналогично
DRIVERSOURCES
для драйверов.
Первый живой layer -- dummy_layer.c. 24.07.2012: retired.
Надо б иметь версионирование layers! Это нужно на случай изменения ABI. Возможное решение: драйвер в layerinfo указывает строку вида "layername#version", а в LayerRec надо завести поле "abi_version". Если у драйвера "#version" не указывается, то ему подходит любой экземпляр layer'а. Короче -- общий принцип примерно как versioning в glibc-2.1. Хотя описанный здесь механизм не очень-то позволяет загружать и использовать несколько версий layer'а одновременно, для детектирования ошибок его уже вполне достаточно.
Еще проблема: такая примитивная схема не дает возможности организовать "абстрактные драйверы на лету" -- например, "абстрактные CAN-драйверы", которые могли бы функционировать с любым CAN-layer'ом; другой пример -- NAF-драйверы, которые функционируют подачей NAF'ов через произвольный CAMAC-layer.
29.11.2002: да нафиг не нужны -- намного удобнее однопроцессная архитектура. Все резоны описаны в cx/src/doc/misc/miscinfo.txt, часть 1.
- На widget'е гистограммы есть очень много ниеспользуемого места непосредственно справа, слева и тд от самой гистограммы. Хотелось бы его уменьшить.
- Подписи на шкалах бывают лишними => нужна возможность их отключения.
- Не всегда нужна стандартная реакция гистограммы на нажатие кномпи мыши (растягивание, перетаскивание гистограммы).
- Оси координат всегда находятся слева и справа от гистограммы хотелось бы иметь возможность, например, всегда проводить из через точку (0,0).
19.05.2003: дополнение от него же, написанное в
ответ на ответ на высказанную просьбу, чтобы А.Антонов написал доки к
этому виджету:
Теперь надо это передать Антонову в качестве затравки для написания
дока.
Заметки по PlotWidget (XY.h)
1. Нужна документация по осям!
как включить/выключить отображение той или иной оси
как отключить перетягивание оси мышкой
2. Документация по реперным линиям!
как их культурно создавать
какие у них свойства
3. Интересует документация на такой интересный ресурс как
XmNaxisFlags, а то что-то с ним намудрено ...\
4. Что такое сетка? И с чем его едят?
9. Косяки при отображениее подписей к осям!
Ежели подпись не входит целиком,
то, наверное ее лучше вообще не рисовать, чем
нарисовать часть подписи!
А то получается
"1.5 1.6 1.7 1 "
Что не есть Good!
Чугуев Андрей Сергеевич
11-52 19.05.03
12.06.2003: LOGC_DIM
поддерживается
-- чего там делать-то было, ввести еще один цвет, плюс один case.
Зато возникла идея: а не применять ли колоризацию не только к значениям каналов, но и к rowlabels в ELEM_SINGLECOL?
Часом позже: сделано -- все rowlabels (и MULTICOL тоже) расцвечиваются так же, как 1-й канал в строке.
Просто так, "на халяву", это не делается -- для сервера-то это разные каналы, и а) драйверу придется выпендриваться, помнить, что "был запрос на такие-то каналы", и по приходу данных производить соответствующий обсчет и делать ReturnBigc для запрошенных; а еще вдруг придет запрос на доп. канал сразу после того, как получен "основной" -- что ему, запускать измерение заново, или, если все еще тот же цикл, отдать из кэша?; б) как сервер будет проверять "эквивалентность запросов"?
Пример 1: в некоторых случаях для экономии пропускной способности достаточно отдавать (клиенту в Антарктиде) лишь центральный участок картинки, в котором и содержится "петелька". 1б: участок может определяться тем, какой viewport стоит у пользователя (в стиле того, как Гусев вычитывает через ППИ лишь ту часть федотовской камеры, которая реально отображается).
Пример 2: драйвер может произвести некий обсчет картинки и выдать тот ее сегмент, на котором запечатлена горизонтальная полоса, т.е., сам примет решение, в отличие от первого примера, где решение принимает клиент.
Во время разговора со Старостенко у меня в голове возник термин "пост-процессинг"; сейчас, к сожалению, полная его мотивация подутеряна.
Как бы то ни было, явно прослеживается неадекватность модели, когда драйвер является "тупым исполнителем" запросов, передаваемых сервером, а сервер принимает их обратно, кэширует, и сам сравнивает на эквивалентность (т.е., грубо говоря, служит кэширующей "трубой"). Стоило бы добавить возможность драйверу поучаствовать в передаче ответа клиенту -- т.е., надо отделить собственно вычитывание данных из блока (здесь участвуют параметры измерения -- K, T, SYNC для камеры, дискретность, диапазоны и нули для АЦП-200) и представление данных клиенту в соответствии с его персональными запросами. Здесь, естественно, надо работать аккуратно -- к примеру, если "представление" -- это PNG-версия картинки, или результат какой-либо иной "сложной" обработки (например, удаления битых пикселов, вычитания фона), требующей существенных ресурсов процессора, то это представление должно кэшироваться на случай, если другой клиент тут же попросит точно такое же.
Отдельно, похоже, требуется возможность при помощи специального значения параметра cachectl попросить прислать просто текущую картинку, не дожидаясь очередного измерения, прямо из кэша, вне зависимости от того, когда она была измерена и с какими параметрами (это запрос от известно кого; сейчас, впрочем, возник встречный вопрос: а зачем -- такое-то извращение, ведь могла быть считана вообще неделю назад... Надо спросить у Молодого Стенки!).
BTW, Саша обещал постараться сформулировать возможные требования к тому, что может в связи с камерой потребоваться от сервера.
30.04.2003: Сегодня Старостенко подумал-подумал, и решил, что "требования" постарается сформулировать, когда я ему предоставлю уже хоть что-нибудь (вот спихотехник!).
А я так поразмыслил и, похоже, вышеописанный механизм с возможностью драйверу принять участие в формировании ответа для клиента -- вполне адекватен. При этом "параметры" запроса бьются на две части: собственно "параметры измерения" и "формат ответа".
В cx-протокол при этом изменения вносить не надо -- просто в
описании блока в blklist будет дополнительное опциональное указание,
сколько первых int32 параметров являются "параметрами измерения" (по
умолчанию -- все; опциональное указание -- через двоеточие -- например,
"1*8:5+0b/8+330000s
"), остальные же считаются "форматом
ответа" (хотя в libcda соответствующее указание тоже должно быть --
чтобы оно не умничало и не пыталось испортить "фиксированные"
параметры).
Замечание от 02.06.2002: осел! Все же проще -- надо через двоеточие указывать количество дополнительных параметров формата -- т.е., запись "3:5" означает "Три параметра измерения, пять параметров формата ответа, итого -- восемь.".
В связке же "сервер-драйверы" будет происходить следующее:
ReturnBigc()
, т.е., то, что отдается сейчас, без этих
callback'ов, оно же то, что отдается при отсутствии "параметров
формата" или при отсутствии callback'а.
Такой алгоритм очевидным образом решает приведенные выше проблемы:
В ответе от сервера в "info" имеет смысл возвращать три сегмента -- 1) параметры измерения, 2) парметры формата, характеризующие этот ответ (т.е., те, которые были в запросе), 3) некие параметры, описывающие конкретный ответ (например, в случае с "автонайденной" горизонтальной полосой -- ее координаты).
30.04.2003: И еще кое-какие соображения, навеянные вчерашним разговором со Старостенко и сегодняшним с Гусевым (записано 04.05.2003):
Резюме на это: мдя, я чувствую, что я просто гляжу дальше и учитываю больше, чем эта парочка, и мне имеющееся в CX решение представляется наиболее адекватным и сбалансированным. Причем Саша прямо открытым текстом заявляет -- нефиг, мол, пытаться предусмотреть на год вперед. Увы -- он в этой области просто дилетант, и не обладает достаточным опытом/квалификацией для принятия адекватных решений, т.к. видит все только со своей колокольни, желая иметь результат прямо сейчас, а остальное его не волнует, и дальше -- хоть трава не расти.
Резюме на сие: в CX применена весьма хорошая и потенциально более надежная схема, чем в EPICS'е. Т.е., она уже сейчас (при соответствующей поддержке в драйверах) позволяет иметь с большими каналами ту же функциональность, что и в EPICS'е, но плюс еще дает атомарность.
Единственное что у меня все больше и больше крепнет убеждение, что надо бы все же иметь некий механизм чтения/записи атрибутов (уставки -- частный случай), который бы позволял читать/писать индивидуальные атрибуты как независимо от данных (например, чтобы менять их по мере кручения ручек юзером), так и атомарно вместе с данными (для, например, отдачи информации об ошибках (чем так гордится Женя)). Причем именно индивидуальные -- если мы хотим поменять только атрибут N, или при измерении нас колышет значение лишь атрибута #5 из десяти, а остальные девять неважны. Вопрос, правда, во что выродится при этом код сравнения запросов на эквивалентность -- похоже, в тупое сравнение в цикле... И связанный вопрос -- как на эти атрибуты ссылаться -- по именам, по номерам, али как...
А вот с обычными каналами у Гусева до сих пор непонятки -- и у варианта "раздельные поля для ввода и отображения текущего" есть плюсы (ничего не вырывается), и у "единое поле" тоже (не нужна операция копирования из отображающего в поле ввода, не говоря уж об экономии места и приближенности к нормальным "ручкам").
Резюме: насчет атрибутов больших каналов я явно иду по верному пути. С обычными каналами подход тоже хороший -- вполне жизненный и работоспособный.
21.02.2007: ну вот, при изготовлении драйвера для все той же самой tsycam -- на этот раз tsycamv, для ndbp -- заново (независимо!) пришла та мысль о каналах-alias'ах, и именно так и сделано -- #0:10бит, #1:16бит, #2:32бит.
Господи, почти четыре года прошло, а параметры представления до сих пор не реализованы (а все из-за принудительно-последовательного указания параметров!), а потребность в них меньше-то не стала!
Мотивация: в случае вычитывания tsycam через cx+cda возникает множество избыточных операций копирования:
read()
-- cxlib
копирует его в буфер пользователя.
NewDataProc()=>DecodeData()
скопировать кадр в
промежуточный буфер, который немедленно (с гарантией 99.9% -- ДО
прихода следующего пакета) будет вычитан прикладной программой.
Что интересно, конечный результат копирования будет использован ровно 1 раз -- для выдергивания (PNG или 10->16) в буфер, приемлемый для отображения на экране или своих обсчетов; выдергивание -- операция вполне атомарная, способная работать не выходя из обработчика сигнала (надеюсь -- вопрос только, не вызывает ли libpng функции malloc()).
Таким образом, в принципе ничто не сломается, если раскодировка клиентом произойдет непосредственно из буфера пакета.
Для этого необходимо ввести per-connection режимы работы "zero-copy" в cxlib (0-нет, 1-да) и cda (0-нет, 1-да, плюс включить zero-copy в cxlib).
В режиме zero-copy:
NewDataProc()
), со специальным кодом "data-was-buffered";
Совершенно очевидно, что делается это все без проблем (день работы?). Возможно, "zero-copy" имеет смысл только для собственно данных -- т.е., например, картинки, которая несколько сот килобайт; а info так невелико, что его вполне можно и копировать.
Идея: у нас ведь уже предусмотрено ограничение доступа
per-IP, так? Дополнить варианты "allow
" и
"deny
" третьим -- "readonly
", который был бы
эквивалентен allow, но запрещал бы операции записи: у обычных каналов
-- можно или а) возвращать какой-нить INVAL, б) молча считать SET* за
GET*; у больших каналов -- или а) запрещать доступ вообще (о-опс!), или
б) принудительно проставлять поле cachectl
в "return what
you have right now".
Далее: каждому клиенту дается флаг (флаги?) "прав" -- что ему позволено, и с cx-console можно эти права менять (возможность менять права определяется отдельным "правом" -- в общем, как capabilities).
И, наконец: все внешние клиенты работают ТОЛЬКО через специальный шлюз, являющийся отдельным cx-сервером, функционирующим как редиректор, с умолчательными правами клиентов readonly, и делаем специальную программу (xcx-console?), в которой можно каждому клиенту менять права. Этак программа будет запускаться только на операторской машине, и оператор (Гуров :) будет лишь по звонку просящего давать ему write-права.
Здесь встает старая проблема: драйверы должны бы иметь возможность кроме данных как-нибудь отдавать еще "статус" канала -- т.е., код ошибки. В том числе, и абстрактные драйверы.
И еще: надо б дать драйверам (в т.ч. и абстрактным) возможность в
функции "init_blk" проверять наличие/ 19.06.2003: очевидным образом, для отдачи
"статуса" канала (назовем его " Есть еще три проблемы, связанные с введением " А вот с cda проблемы быть не должно -- там и так уже есть поле
Глобальное замечание: надо
обязательно выяснить, какой протокол на эту тему
используетмя во всяких EPICS и прочих TINE -- какие еще поля/фичи могут
понадобиться.
??.10.2003: не записал, к сожалению, когда точно,
но все сделал. Что собственно проделано (объединено введение rflags с
модификацией протокола):
Реально осталось еще свести DataFork и BigCommand в
единую struct/union CxFork.
Одно "но": за RELAX пока Cdr отвечает не
полностью, часть кабы не в Knobs_alarm_widget.c... 26.10.2004: все -- сегодня вся алхимия с ALARM/RELAX
полностью переложена на Cdr.
Что пока не сделано: сервер при
инициализации драйверов/блоков пока не проставляет в случае проблем
флажки 23.11.2003: добавил еще один флаг --
23.11.2003: видимо, понадобится иметь в cxlib
функции, которые бы по "rflag'у" отдавали бы его описание:
Это нужно для того, чтобы
Cdr/Knobs/или-кто-там-будет-за-это-отвечать могли бы унифицированно
выдавать диагностические сообщения.
24.11.2003: сделал эти две функции,
строки-описания лежат в массиве 30.11.2003: в продолжение мысли на тему флага
"device malfunction": надо заменить " И надо в дополнение к Потенциальная проблема: если б были отдельные
30.11.2003: ну что ж -- переименовал
" 08.12.2003: в дополнение к
"расширенному статусу" надо иметь еще запрос: "скажи, к какому блоку
относится вот этот канал". Это потребно для того, чтобы
оператор смог быстро разобраться, что же там глючит.
02.07.2004: пришло в голову решение
(очевидное!) для задачи "что делать с c_rflags[] по
Для соблюдения хронологии описание будет в bigfile-0001.html.
02.07.2004: поскольку эта фича уже сделана
(подробности -- в bigfile-0001.html), то помечаем раздел как "done".
Замечание 1: надо будет выделить кусочек, который делает
Замечание 2: у нас уже есть функция А Олегу была отправлена следующая схема поведения драйвера с точки
зрения детектирования потери пакетов:
24.07.2003: По результатам разговора с Чугуевым:
почему-то после выключения linac1 остались висеть драйвера на
мотороллерах. Почему -- неясно. Как бы то ни было, надо включить
SO_KEEPALIVE в соединении m5200drv.c с uclinux_slaves/ (с обоих
сторон?); и сделать поддержку реконнектов в m5200_drv.c
24.07.2003: посмотрел в
uclinux_drivrebody.c... Там несколько сложновато вытащить в
"библиотеку" -- Итак:
Насчет Апача -- эти игрища задокументированы у него в файле
htdocs/manual/misc/descriptors.html, а реализованы -- в
src/ap/ap_slack.c.
В другую сторону ("knowledge::unix"): небезынтересна функция
errstat
") понадобится-таки
поменять сетевой протокол. Хорошо бы совместить сие событие с
изменением протокола для больших каналов (передача ninfo и
retbufsize серверу; разделение "параметров измерения" и "параметров
управления" плюс, возможно, отдельная операция уставки атрибутов).
Плюс, этот "errstat
" потребен (в тех же целях) для больших
каналов -- по штуке на каждый.
errstat
"
-- в API:
int *errstats
к ReturnChan{Group,Set}()
,
можно "обойтись" отдельным вызовом
"ReturnChan{Group,Set}Errstats()
" -- это позволило бы
более гибко манипулировать статусами, например, иметь "кумулятивное"
поле "была ошибка". Вопрос только -- надо ли? Или корректнее все же
сделать это именно обязательным параметром?
cx_getvset_ex(...)
".)
flags
, и можно просто сделать его совместимым с
errstat
-- т.е., одного формата, без перекрытия по битовым
маскам.
rflags_t
(uint32), используемый
везде.
c_rflags[]
и
c_crflags[]
(последнее -- "cumulative rflags"), оно
накапливает OR'ом все когда-либо происходившее с c_rflags
.
Хотя само OR'енье уже сделано, никакого метода доступа к
c_crflags[]
пока нету, как и обнуления (из
FillChanProps()
и ResetBlock()
оно,
естественно, нулится).
bigchaninfo_t.{cur_rflags,crflags}
.
Если первое честно берется от драйвера и отдается клиенту, то второе
пока также просто праведно OR'ится и ничего более.
ReturnChan{Group,Set}()
последним параметром
добавлен rflags_t *rflags
. К сожалению, ему разрешено
быть NULL
, при этом c_rflags[]
не трогается.
ReturnBigc()
также добавлен параметр rflags_t
rflags
-- ну тут-то уж все, что может сделать халтурный драйвер
-- отдавать всегда 0.
ABSTRACT_NNN_FUNC
, то все
продолжает компилироваться. Другое дело, что теперь
надо во все абстрактные драйверы вставить честную отдачу rflags.
age_of(int32 t)
.
bigchaninfo_t.cur_time
, должным
образом проставляемое, и через age_of()
отдаваемое
клиенту.
tag_t *tag
и в вызовы
cx_bigcmsg()
/cx_bigcreq()
. Там, из-за этого
на пару с новым же параметром rflags, был порядочный винегрет.
OpCode
и
ByteSize
. (Раньше же отсутствовали BigcFork.OpCode
(команда-то была всегда одна) и в обоих случаях ByteSize -- поскольку
оно всегда считалось получателем на основе типа команды.)
DataForkSize()
.
'>'
--
запрос, '<'
-- ответ. Второй символ определяет "класс"
операции/данных: 'V'
-- обычные каналы, 'B'
-- большие. В будущем для БД-запросов задействуем и 'D'
.
А уж оставшиеся два символа худо-бедно кодируют конкретную операцию --
типа gG, gS, etc. --
{get,set}{Group,Set}. И делается это все
морем enum'ов и define-макросов -- все с префиксом CXP_
.
cx_{get,set}{value,vgroup,vset}()
добавлен (в конец, после
tags
) параметр rflags_t *rflags
. Ему,
аналогично tags
, разрешается быть NULL
. С
cx_bigc{msg,req}()
аналогично.
CXRF_{SERVER,CLIENT}_MASK
; server-side флаги определены
там же прочими константами CXRF_*
.
CDA_FLAG_OTHEROP
задвинут в самый
конец -- в 31-й бит.
CDR_FLAG_*
. В их число входят
ALARM_{RELAX,YELLOW,RED}
и DEFUNCT
(посинелость).
CXRF_NO_{DEV,DRV}
(02.07.2004: сделано уже, сделано,
вчера), и DisconnectBlock()
тоже ничего
подобного не делает (кстати -- а что надо делать: ставить
CXRF_NO_DEV
, или завести дополнительный
CXRF_MALF_DEV
/"Device malfunction"?) (25.05.2005: да давно уже
DisconnectBlock()
проставляет флаги, которые ему отдает
драйвер при "самоубийстве").
CXRF_IO_TIMEOUT=1<<2
. Будет использоваться при
"истечении времени ожидания готовности блока", когда например у CAMAC
делается "тест L по Q" -- adc200, c0609 (Липа), ПКС-8.
cx_strrflag_long()
-- длинное, user-readable description
(комментарий из cx_types.h), и
cx_strrflag_short()
-- короткое, просто имя константы без
префикса CXRF_
.
_cx_rflagslist[]
, который
надлежит держать синхронизированным с флагами CXRF_XXX
(о
чем в обоих местах и поставлены комментарии).
CXRF_NODEV
" на
более общий "CXRF_OFFLINE
", могущий означать не только
"блок не обнаружен при инициализации", но и "блок ушел в
отключку".
DisconnectBlock
иметь что-то типа "SetBlock{Offline,Online}()
" --
чтобы например m5200_drv мог при разрыве объявлять "offline",
заказывать reconnect, а при success -- объявлять "online". И чтобы при
online делался re-заказ всего, что помечено как
{rd,wr}_req
(иначе: драйвер-то давно "отправил запрос и
забыл", исполняющий блок гикнулся, не ответив, а запрос-то -- висит!).
CXRF_{NODEV,OFFLINE}
, то юзер сможет легко опознать
проблему. А так -- пес его знает, то ли чего-то не включено, то ли уже
в процессе работы тукнулось... Потенциальное
решение: иметь возможность спросить у сервера "расширенный
статус канала/блока", в котором будет, во-первых, человеческим языком
написано, в чем проблема, а во-вторых -- будет стоять информация о
местоположении блока. В таком случае можно будет заставить
Chl/Cdr/Knobs вытрясать эту инфу из сервера. А сами запросы можно
будет легко вставить в протокол с новыми cxsd_fe_cx и cxlib --
присвоив, например, код-класса 'Q'
(query).
CXRF_NODEV
" в "CXRF_OFFLINE
". А что толку?
;-)
DisconnectBlock()
": отдать это на откуп драйверу --
позволить ему передавать параметром флаги, которые надо уставить.
cx_strrflag_*()
уже есть.
connect()
и запуск мотороллерной части, в отдельную
функцию, и хранить массив
struct sockaddr_in peers[MAX_ALLOWED_FD+1]
, плюс
не забывать в нужные моменты вызывать
{Register,Deregister}DriverFD
.
timed_connect()
,
которая как раз и понадобится для "независающего" коннекта к
мотороллеру. А в идеале, если б уже существовал fdiolib, можно было бы
перевести сокет в non-blocking, сделать connect(), и зарегистрировать
дескриптор как "ждущий готовности на запись". Кстати, а что,
собственно, мешает уже сейчас изготовить в cxsd_driver.[ch]
доп.API -- типа {Register,Deregister}DriverWrFD()
? Это бы
на 100% решило проблему "подвисаний сервера из-за блокирующести
драйвера".
int request_sent[MAX_MAGICS]; // Инициализируются нулями
do_rw(int magicid)
{
if (request_sent[magicid]) return;
Send_Request(....);
request_sent[magicid] = 1;
RegisterDriverTimeout(magicid, 1000000); // Если через секунду данные
// не придут -- дерни меня
}
fd_io(???)
{
ReadPacket();
magicid = <узнать-magicid-по-пакету>;
ReturnChanGroup(...);
request_sent[magicid] = 0;
RegisterDriverTimeout(magicid, -1); // Данные пришли -- сбросим таймаут
}
timeout_proc(int magicid)
{
// Ouch! The packet was lost!
Send_Request(....); // Перепошлем запрос
request_sent[magicid] = 1;
RegisterDriverTimeout(magicid, 1000000);
}
DriverBody()
считает себя за основной
цикл.
и подобных надо будет сделать обобщенную процедуру регистрации
дескриптора с указанием callback-процедуры (йоу! это ж будет уже почти
половина интерфейса fdiolib'а!).
for (s = 0; s <= maxconnected; s++)
InteractWithPorter()
очевидным образом становится не
нужен.
{Begin,End}OfCycle()
.
HandleSimulatedHardware()
), а частично -- в модуль
(функции Serve{Data,Sunscribe}Request()
,
Send{DataReply,Subscription}()
,
HandleSubscribedChannels()
-- они станут static, плюс
обслуживающие их).
Exec{Clients,Close}()
--
ведь оне теперь становятся frontend-specific?
FD_SETSIZE-1
(т.е., 255/1023) -- они пользуются
select()
, и просто неспособны работать с такими "высокими"
дескрипторами. В Apache используется решение -- держать первые 15
дескрипторов свободными (см. дистрибутив Apache, src/CHANGES,
раздел 1.2.1, ключевое слово "slack line"), точнее -- "держать все
перманентные дескрипторы выше 15.
select()
нафиг не нужен) выше
FD_SETSIZE
(в том же Apache -- ключевое слово
HIGH_SLACK_LINE
).
sigtimedwait()
и ее дегенеративный вариант
sigwaitinfo()
(man-страница на них отсутствует, искать
Google'ом).
05.12.2002: Сделано -- работают.