QNX RTP Logo QNX Realtime Platform: Русский Портал QNX
Thursday, 20 Nov 2008 17:45
Меню

Проект OpenNET - все о Unix
Опыт использования менеджера ресурса Print E-mail
Олег И. Цилюрик, olej at front.ru

Почему  менеджер ресурса?

Операционная система QNX построена на тщательно проработанной в теории, но крайне редко реализуемой в OS концепции – коммутации сообщений. Ядро (микроядро) системы при этом подходе выступает в качестве компактного коммутатора сообщений между взаимодействующими программными компонентами.

В такой OS все привычные программисту запросы API (например, стандарта POSIX) пакуются в тело сообщений, а микроядро системы обеспечивает их синхронизацию и доставку от отправителя к получателю (рис.1).

рис.1

В технической документации OS QNX появляется такой термин как Resource Manager – Менеджер Ресурса. Менеджер Ресурса (МР) является такой универсальной программной структурой, которая потенциально является приёмником и обработчиком всех типов сообщений, циркулирующих в системе. Для написания РМ предлагается специальная техника, описание которой в HELP подсистеме QNX занимает свыше 80 страниц (и это при крайне лаконичной, даже “евангелистской” манере изложения в HELP QNX: “имеющий уши – да услышит”).

Почему этой технике уделено столь пристальное внимание? РМ обрабатывает как сообщения, несущие общеизвестные POSIX запросы (open, read, write, close …); так и сообщения, содержащие более специализированные не-POSIX запросы (devctl …); наконец, сообщения произвольных не специфицированных типов, несущих в себе любой пользовательский каприз. Идея, гениальная в своей простоте: для любых видов взаимодействий в OS иметь единый механизм передачи, приёма и обработки сообщений. Удивляет не то, что мы наблюдаем в QNX – удивляет то, что мы не наблюдаем нечто подобное в других семействах OS.

Итак, PM реализует универсальный серверный механизм внутри QNX (а все UNIX системы построены, во многом, на клиент - серверном взаимодействии). Что может быть написано в унифицированной технике РМ? Практически всё: драйверы устройств и псевдоустройств, файловые и сетевые системы, взаимодействие между пользовательскими специализированными подсистемами или оборудованием… - и всё в единой технике! После этого становится понятным, почему QSSL уделяет технике программирования РМ столь исключительное внимание.

Ещё одной отличительной особенностью этого механизма есть то, что все типы взаимодействий, реализованных в технике РМ в рамках одиночного локального узла, автоматически раздвигаются сетевым протоколом Fleet на всё пространство узлов сети QNET (“родная” сетевая система QNX, прозрачно и параллельно сосуществующая в ней с TCP/IP). Но этот факт сам по себе столь обстоятелен, что об этом – в другой раз.

Примечание: полностью разделяя приоритеты QSSL, и считая технику написания МР ключевой для всей системы QNX, я сделал перевод документа “Writing Resource Manager” из комплекта документации QNX, и дополнил его комментариями и разъяснениями “смутных” мест. В ближайшее время он будет размещён на сайте российских разработчиков QNXhttp://qnx.org.ru. Правомочность такой работы согласована с президентом QSSL.

Описание задачи для эксперимента.

Для отработки техники МР “в живую” требовалась адекватная задача под проект:

  • Как уже понятно из изложенного выше, и станет ещё более понятно из последующего изложения – менеджер ресурса, это программный интерфейс, управляющий тем “нечто”, что, собственно, и названо “ресурс”. Такой интерфейс способен, в частности, трансформировать поток стандартных POSIX запросов в реакции “ресурса”, которые, при достаточной не традиционности “ресурса”, могут в корне отличаться от привычных реакций на общеизвестные запросы.

  • Поведение аппаратного устройства (например, последовательного порта), или системного псевдоустройства (например, файловой системы), управляемых МР, понятно и предсказуемо даже из общего рассмотрения техники программирования. Хотелось бы иметь максимально “капризный” ресурс, связанный, например, с подсистемой графических окон, который вовлекал бы в свою работу максимальное число слоёв OS, и требовал параллелизма, реентерабельности и т.п.

В качестве такой задачи был выбран экстремально экстравагантный “ресурс” - оконная система обмена сообщениями “на манер ICQ”. Отличия состоят в том, что:

  • Обмен осуществляется не через коммуникационный сервер, а непосредственно приложение с приложением, каждое из которых выступает в отношении партнёра и клиентом и сервером (не касаясь функциональности, отметим, что в реализации такое приложение - сложнее).

  • В качестве сетевого протокола используется не SOCKS или TCP/IP, а “родной” протокол Fleet сети QNET. Использование этого условия позволит нам увидеть дополнительные уникальные возможности программной техники, не достижимые никакими иными способами (или в другой OS).

Более того, мне хотелось бы верить в дополнительную целесообразность такого проекта, состоящую в том, что:

  • Взяв за основу работающий программный код “по мотивам ICQ”, и используя описываемую технику, кто либо мог бы легко трансформировать его в “реальный ICQ”, особенно если учесть, что для QNX нет хорошо зарекомендовавшего себя ICQ-клиента. Замена несущих сетевых протоколов не должна быть существенно сложной, так как, при всей своей громоздкости, стандартные протоколы SOCKS или TCP/IP идеологически проще, чем протокол Fleet.

  • На базе подобного приложения можно без труда построить целый ряд вариаций сетевых клиентов, типа IRC, или нечто подобного.

Все предыдущие “предложения” - это всё таки красивые игрушки, но на базе этой техники возникает возможность построить модель распределённых вычислений (или управления), когда во встроенные (embedded) периферийные компьютеры на базе промышленных PC можно загружать произвольные программные агенты. Агенты взаимодействуют с родительским хостом, и функционируют как составные части единого вычислительного процесса. Отработке этой возможности, собственно, и посвящена проделанная работа.

Рис.2

На рис.2 показан скриншот окончательного проекта (подробнее этот рисунок будет неоднократно комментироваться далее). Рисунок “снят” с приложения, работающего в реальной сети, в окне выбора хоста виден список из 2-х компьютеров: “rtp” и ”feb”. Реализация этого проекта и описывается далее.

Написание менеджера ресурса.

Первый шаг в написании менеджера ресурса состоял в том, что я просто скопировал достаточно объёмный исходный текст примера много потокового менеджера ресурса из HELP подсистемы QNX, откомпилировал, и … это заработало (ещё один и заслуженный реверанс в сторону качества поставляемой с QNX технической документации). Всё последующее 2-х недельное “выкручивание рук” этому, пока ещё ничего осмысленного не делающего, МР было делом рутинной техники.

Подобная “технология” подтверждает ещё одно предположение: приложение МР (посредством которого в QNX можно описать всё или почти всё) в основной части кода является единообразным шаблоном, который нужно не столько писать, сколько незначительно подправить под специфику своего “ресурса” (около 75% строк кода файла Phone.cpp составляет “шаблонный” код менеджера ресурса).

Первым побуждением при написании проекта было создание единого проекта-приложения (в терминологии PhAB), первая часть которого (инициализация) создавала бы МР, регистрирующий уникальное имя (в проекте /proc/talk), а вторая – реализовывала бы пользовательский интерфейс к аналогичным МР на других хостах. Достаточно продолжительные эксперименты показали, что этот путь – не плодотворный. Это – не работает в принципе, и это особенность МР, которая не отражена нигде в документации, и на которой следует остановиться подробнее.

В документации отмечается, что Менеджер Ресурса регистрирует имя, обращаясь к Менеджеру Процессов (соответствующего хоста), а последний заносит это имя в свои таблицы. Можно предположить (и это очень похоже на правду), что изменения состояния таблиц зарегистрированных имён фиксируются Менеджером Процессов только после завершения процесса, регистрирующего имя. Это логично, так как событиями, на которые должен реагировать Менеджер Процессов, являются запуск и завершение процессов в системе.

Отсюда следует, что рассматриваемый проект распадается на два процесса: процесс, регистрирующий имя /proc/talk, и клиентский процесс, обеспечивающий пользовательские обращения к этому имени. Как оказалось, этот подход оказался плодотворнее и из других соображений.

Ещё один нюанс. Вот строка из документации (больше этот аспект нигде в документации не затрагивается): “Для подключения менеджер ресурса должен быть запущен с полномочиями root”. Но здесь не скрывается существенное ограничение: мы собираем приложение МР от имени root, после чего устанавливаем бит suid в полученном приложении (альтернативный путь – сборка от имени пользователя, после чего с помощью chown изменяем владельца на root). Клиентское приложение собирается от имени рядового пользователя.

Полученный в результате проект демонстрирует, помимо использования техники МР (но, собственно, благодаря ей и архитектуре QNX), две очень интересных особенности:

  1. Существенно сетевое приложение не содержит в своём коде ни единого сетевого оператора (операций с сетвыми сокетами), всю рутинную работу транспортировки сообщений по сети берёт на себя QNET.
  2. Неограниченно многопоточное приложение с клонированием экземпляров (сеансов связи, которые вы можете открывать в неограниченном количестве) не содержит в своём коде ни единого оператора явного порождения потока (по крайней мере, в той части кода, которая ответственна за клонирование экземпляров). Всё распараллеливание неявно обеспечивается системой передачи сообщений OS QNX и, отчасти, организацией её графической подсистемой Photon.

Комментарии к коду.

Рис. 3

На рис.3 показан тот “ресурс”, которым мы управляем посредством интерфейса менеджера ресурса. Здесь же проставлены имена widget-ов, необходимые для понимания исходного кода.

Рис.4

Аналогично, рис.4 показывает изображение и именования элементов клиентского приложения, которое позволяет выбирать сетевые хосты и порождать новые соединения (сеансы).

Кратко, в мере, необходимой для понимания исходного кода, рассмотрим работу приложений:

  • Запускаем на различных хостах приложения МР (исполнимый файл rmta). Для целей тестирования или отладки МР может быть запущен на единственном локальном хосте, при этом оба конца “канала” будут привязаны к единому компьютеру. Если на компьютере уже выполняется rmta, то вторая копия не может быть запущена (функция OnStartRM() в файле Phone.cpp).

  • В виде, показанном на скринщоте, РМ при запуске выводит окно (на рис.2 – окно в верхнем левом углу), на котором выведено сообщение о том, что “При закрытии этого окна ресурс-менеджер останавливается”. Это сделано в иллюстрационных и отладочных целях. В противном случае останавливать (перегружать) МР можно только командой “kill” по pid процесса. Более естественный старт – “глухо - немой”, в режиме демона. Для этого нужно в файле Phone.cpp закомментировать строку:

ThreadCreate( 0, &StartResMng, (void*)sResName, NULL );

и, напротив, убрать комментарий со строки:

StartResMng( (void*)sResName );

  • Запускаем (на любом из хостов, на котором выполняется МР) клиентскую программу (файл clitalk).

  • В окне base_host отображается список хостов, доступных в сети QNET (список обновляется по таймеру, функция callback OnTick() файла Initial.cpp, т.е. отдельные имена в списке могут динамически появляться и исчезать).

  • Текущий выбранный хост из списка сносится в поле base_select.

  • При нажатии base_connect создаётся ещё один экземпляр объекта класса Line (по цепочке: функция OnConnect() файла Phone.cpp > функция CreateLine() файла Line.cpp > конструктор объекта Line). Каждый новый объект Line создаётся анонимным: после создания к нему нет доступа из программного кода (описано в [2]). Конструктор Line запрашивает по open() двух менеджеров ресурсов (локальный и удалённый), которые создают для него 2 диалоговых окна обмена (возможно, на разных компьютерах). Получив от МР два файловых дескриптора (в ответ на open()), конструктор Line запускает отдельный поток (функция Exchanger() файла Line.cpp) взаимного обмена (синхронизации) окон c интервалом в 1 сек. Клиент при этом выполняет только группу POSIX операций read() -> write().

  • Далее клиент уже не выполняет ничего (он вообще не подозревает о каких либо окнах). Вся остальная работа выполняется МР, причём для любого числа клиентских сеансов. Все запросы к МР сведены к POSIX запросам (стандартного синтаксиса):

  • open()открыть окно диалогового обмена и возвратить файловый дескриптор доступа к нему. По open() МР создаёт новый объект класса Client, который и представляет окно (Client выполнен в технике компонентного программирования, которая описана в [2] и здесь не рассматривается).
  • read()прочитать содержимое, которое окно готово передать. Если пользователь нажимал phone_send (с момента последнего обращения read), то read возвращает строку, переданную пользователем. Если информации для передачи нет – возвращается “пустая” строка.
  • write()дописать (конкатенировать) текстовое поле к содержимому результирующего окна phone_text.
  • close()уничтожить окно диалогового обмена и освободить файловый дескриптор.

Сложным местом в исходном коде является только то, как МР, получив запрос open(), открывает окно обмена (т.е. создаёт объект класса Client, файл Client.cpp), и связанный с ним – как МР уничтожает этот объект, получив close().

Получив запрос open(), МР должен открыть ocb (open control block) для создаваемого файлового дескриптора, для чего выполняет функцию, которую мы прописали ему в таблице connect_funcs строкой:

// connect_funcs.open = line_open;

Поскольку, для усложнения и чистоты эксперимента, я закомментировал эту строку (и эта строка и заготовка функции line_open() оставлены в тексте для возможностей использования), то МР будет вызывать функцию по умолчанию iofunc_open_default(). Я хочу именно её (библиотечная функция, отсутствующая в моём тексте) “научить” выполнять для меня необходимые действия. Очень сжато это описано в [1].

Любая функция открытия (пользовательская или по умолчанию) должна создать ocb, для чего вызывает для него функцию allocate() (техника МР подготовлена для классического C, поэтому у нас появляется дополнительная “головная боль” согласовать соглашения C с нашим C++ объектом Client). В тексте ocb и функции его создания и уничтожения переопределены:

struct ocb : public iofunc_ocb_t { Client* pCli; };
//--------------------------------------------------------------------------
IOFUNC_OCB_T *ocb_calloc( resmgr_context_t *ctp, IOFUNC_ATTR_T *device ) {
    struct ocb *ocb;
    if( ( ocb = (struct ocb*)calloc( 1, sizeof ( *ocb ) ) ) == NULL )
    return NULL; }
    ocb->pCli = new Client;
    return ocb;
};
//--------------------------------------------------------------------------
void ocb_free( IOFUNC_OCB_T *ocb ) { delete ocb->pCli; free( ocb ); };
//--------------------------------------------------------------------------

К стандартной структуре ocb мы, таким образом, добавили указатель на свой объект, а создание (уничтожение) ocb включает в себя вызов конструктора (деструктора) объекта Client. Для того, чтобы зарегистрировать новые операции для ocb, мы создаём структуру монтирования (она создалась бы и без нашего вмешательства, но – стандартным образом):

iofunc_funcs_t ocb_funcs = { _IOFUNC_NFUNCS, ocb_calloc, ocb_free };
iofunc_mount_t mountpoint = { 0, 0, 0, 0, &ocb_funcs };

Позже, при создании структуры атрибутов устройства в МР, мы увязываем структуру монтирования со структурой атрибутов:

iofunc_attr_init( &attr, S_IFNAM | 0666, 0, 0 );
// переопределены функции создания-уничтожения “нового” ocb
attr.mount = &mountpoint;

Всё. Может показаться несколько сложным, но так, или почти так (в [1] показан немного отличающийся способ) мы поступаем во всех случаях написания МР!

Зачем это сделано? Каждый open() из одного или разных процессов (или даже с другого хоста) должен вернуть индивидуальный файловый дескриптор. Файловый дескриптор однозначно связан с ocb. Все реализующие операции для read(), write() … в МР получают в качестве параметра ocb (в зависимости от того, над каким файловым дескриптором выполняется операция). Обсуждённые выше манипуляции позволяют нам для каждого файлового дескриптора иметь свою индивидуализированную копию сколь угодно сложных структур данных. Т.е., порождать по open() параллельный, но полностью автономный, ресурс!

Всё остальное предельно просто, и легко прослеживается по текстам.

Примечания к исходным кодам: в исходных кодах есть 2 места, которые могут внести невнятицу в картину происходящего. Они оставлены ввиду своей малости и недостатка времени. Ниже они объясняются детально:

1. В операциях чтения / записи класса Line стоят “странные” операторы вида:

n = read( h = hdf, buf, sizeof( buf ) );

Это связано с тем, что после возврата из функций read() и write() значение файлового дескриптора портится (обнуляется). Этот эффект - недоработки МР и манипуляции в нём с флагами. Чтобы не возиться с локализацией этой ошибки, вместо этого, файловый дескриптор просто присваивается промежуточной переменной перед операцией. Того же результата можно было добиться С++ трюком:

n = read( ( hdf ), buf, sizeof( buf ) );

2. При закрытии пользователем окна phone (завершении сеанса), реакция на закрытие заблокирована, и МР при очередном read() возвратит условную строку конца файла (строка sEof файла Common.h). По получению этой строки только объект Line закрывает окно, выполняя close() над файловым дескриптором (хотя бы только потому, что окна нужно уничтожать в паре – те которые составляют сеанс). Корректнее было бы реализовать признак EOF операции read() согласно POSIX – возвратом 0. Но в данном проекте это не принципиально.

Выводы и направления развития.

Самый интересный вывод из проделанной работы состоит в том, что написание даже самых традиционных и не “подходящих” приложений в технике МР по своей трудоёмкости в несколько раз ниже традиционного ручного программирования.

Обладая некоторым предварительным опытом создания подобных приложений для Win32 и Linux, я предполагаю, что “ручная” реализация приложения, аналогичного описанному, была бы в 5-8 раз объёмнее приведенной. (Правда, в приведенном тексте каждый оператор “плотнее” и критичнее к ошибкам).

Можно предложить и некоторые последующие улучшения предложенной схемы:

  • В текущей реализации синхронизация содержимого окон сделана по таймеру. Это решение оставлено в какой то мере сознательно – при изучении работы проекта очень наглядно видеть моменты синхронизации. Более развитое решение должно предполагать асинхронную реализацию операции read() в МР (текущая реализация – синхронная, но в технике МР ничто не препятствует реализации асинхронных реализаций). При этом из класса Line будет исключена необходимость отдельного потока синхронизации окон.

Источники дополнительной информации:

[1] – “Writing Resource Manager” в HELP подсистеме операционной системы QNX RTP 6.1.

[2] – http://qnx.org.ru, Олег И. Цилюрик, “Создание инкапсулированных GUI-компонентов в QNX RTP Photon Application Builder”.

Тексты проекта.

В качестве приложения прилагается архив проекта , содержащий полные проекты PhAB. Задачи rmta и clitalk могут быть запущены из этого архива, или пересобрагы с помощью PhAB. Проекты испытывались в QNX RTP 6.1.  Ниже приводится только краткое перечисление исходных файлов проектов.

Проект менеджера rmta:

Файл Phone.cpp. Это главная программа, собственно реализующая РМ.

Файлы Client.h и Client.cpp. Файлы “Client.h” и “Client.cpp”, собственно, и есть тот “ресурс”, интерфейс к которому создаёт РМ. Это специализированная оконная система, создающая пары связанных клиентских окон. Вся клиентская система выполнена в технике построения графических компонент, описанной мною ранее в [2], и здесь детально не обсуждалась.

Проект клиента clitalk:

Файл Initial.cpp. Это главная программа приложения клиента.

Файлы Line.h и Line.cpp. Файлы “Line.h” и “Line.cpp” реализуют класс “сеанс связи”. При создании объекта этого класса создаётся пара взаимно связанных окон обмена сообщениями. При закрытии объекта оба окна закрываются (уничтожаются) и возможность связи прекращается. Пользователь может создавать сколь угодно много таких объектов, каждый из которых, к примеру, свяжет его с различными хостами сети QNET.

Общие файлы проектов:

Файл Common.h. В этом файле определены общие константы двух проектов (и МР, и его клиента). Самая важная из них – sResName – префикс путевого имени, под которым МР регистрирует себя в файловой системе (и по этому же имени клиент отыскивает свой РМ). Две других константы: sVers – версия проектов, используемая при отработке, и sEof – строка “конец обмена и разрыв сеанса” (правильнее было бы в этом качестве использовать POSIX признак “конец файла”, возвращаемый по read() МР как нулевое число байт результата, но на это просто физически не было времени).

Файлы Utils.h и Utils.h. Файлы “ Utils.h” и “ Utils.cpp” - общие утилиты, используемый из различных файлов проектов. В конечном итоге здесь осталась только одна процедура: вывод диалогового окна диагностики ошибки, с последующим аварийным завершением процесса, или без оного (параметр bFatal).

Олег И. Цилюрик, фирма “LOT Ltd.”, г. Харьков, Украина, mail: olej@lot.kharkov.ua. Автор всегда готов ответить на любые дополнительные вопросы или обсудить детали по электронной почте.

 

[Вернуться к списку]
©   2000-2003 Команда проекта QNX.ORG.RU // QNX.ORG.RU Team
Авторы проекта: Дмитрий Алексеев [dmi] и Дмитрий Васильев. Техническое сопровождение проекта: Игорь Сорокин [isorokin]. Информационное сопровождение: Дмитрий Алексеев [dmi]
QNX - зарегистрированная торговая марка QNX Software Systems, Ltd., Canada. Остальные упоминаемые на сайте торговые марки и логотипы являются исключительно собственностью их уважаемых владельцев. Ничьи права не затронуты. Материалы сайта не могут быть скопированы и где-либо использованы в той или иной форме без письменного разрешения разработчиков сайта.
Powered by Mambo Open Source