Роб Кёртен - Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
_RESMGR_IO_NFUNCS, &io_func);
iofunc_attr_init(&attr, S_IFNAM | 0666, 0, 0);
// Зарегистрировать префикс в пространстве имен путей
if (resmgr_attach(dpp, &resmgr_attr,
"/dev/mynull", _FTYPE_ANY,
0, &connect_func, &io_func, &attr) == -1) {
perror("Ошибка resmgr_attachn");
exit(EXIT_FAILURE);
}
ctp = resmgr_context_alloc(dpp);
// Ждать сообщений в вечном цикле
while (1) (
if ((ctp = resmgr_block(ctp)) == NULL) {
perror("Ошибка resmgr_blockn");
exit(EXIT_FAILURE);
}
resmgr_handler(ctp);
}
}
И все! Полнофункциональный администратор ресурса /dev/null реализуется всего несколькими вызовами функций!
Если бы пришлось писать аналогичный по функциональности администратор (то есть с поддержкой функций stat(), chown(), chmod(), и т.д.) «с нуля», то вам пришлось бы перелопатить сотни, если не тысячи строк Си-кода.
Реально все это за вас делает библиотека
Как вариант начального знакомства с библиотекой, давайте посмотрим, что делают вызовы, использованные в администраторе ресурсов /dev/null:
dispatch _create()
Создает структуру диспетчеризации; она будет использоваться для блокирования по приему сообщения.
iofunc_attr_init()
Инициализирует используемую устройством атрибутную запись. Мы обсудим атрибутные записи в подробностях несколько позже, а вкратце так: атрибутная запись содержит информацию об устройстве, и на каждое имя устройства имеется по одной атрибутной записи.
iofunc_func_init()
Инициализирует две структуры данных, cfuncs и ifuncs, которые содержат соответственно указатели на функции установления соединения и функции ввода/вывода. Это, пожалуй, самый «магический» вызов, поскольку именно он назначает подпрограммы обработки сообщений, привязывая их к структурам данных. Заметьте, что никакого кода обработки сообщений установления соединения или сообщений ввода/вывода, генерируемых функциями read(), stat() или им подобными, в администраторе нет. Дело в том, что библиотека содержит для всех сообщений готовые POSIX-обработчики по умолчанию, и как раз функция iofunc_func_init()-то и привязывает их к двум передаваемым ей таблицам.
resmgr_attach()
Создает канал, который администратор ресурса будет использовать для приема сообщений, и говорит администратору процессов, что мы намерены отвечать за «/dev/null». Параметров тут много, но к этой головной боли мы вернемся несколько позже. Сейчас же важно отметить, что именно здесь связываются воедино дескриптор диспетчера (dpp), имя пути (строка «/dev/null») и обработчики функций установления соединения (cfuncs) и ввода/вывода (ifuncs).
resmgr_context_alloc()
Выделяет внутренний контекстный блок администратора ресурса. Мы рассмотрим этот блок в подробностях несколько позже, а вкратце — он содержит информацию, относящуюся к обрабатываемому сообщению.
resmgr_block()
Это блокирующий вызов администратора ресурса — функция, с помощью которой мы ожидаем сообщение от клиента.
resmgr_handler()
После того как сообщение от клиента получено, для его обработки вызывается эта функция.
За кулисами библиотеки
Вы уже видели, что наша программа ответственна за предоставление основного рабочего цикла приема сообщений:
while (1) {
// Здесь ждем сообщения
if ((ctp = resmgr_block(ctp)) == NULL) {
perror("Unable to resmgr_blockn");
exit(EXIT_FAILURE);
}
// Обработать сообщение
resmgr_handler(ctp);
}
Это очень удобно, поскольку позволяет вам помещать точки останова на принимающей функции и перехватывать сообщения (например, с помощью отладчика) в процессе работы.
Библиотека осуществляет все магические манипуляции внутри функции resmgr_handler(), потому что это как раз то самое место, где сообщение анализируется и обрабатывается в соответствии с таблицами функций установления соединения и ввода/вывода, о которых мы уже говорили ранее.
В действительности, библиотека состоит из двух взаимодействующих уровней — базового, который обеспечивает «сырую» функциональность администратора ресурсов, и уровня POSIX, который содержит вспомогательные функции POSIX и обработчики по умолчанию. Сейчас мы кратко обрисуем эти два уровня, а затем в разделе «Структура администратора ресурсов» рассмотрим все в подробностях.
Базовый уровеньСамый нижний, базовый, уровень состоит из функций, имена которых начинаются с «resmgr_». Этот класс функций относится к низкоуровневым механизмам функционирования администратора ресурсов.
Здесь я только кратко приведу описание этих функций, которые являются доступными и которые мы будем использовать. Затем я отправлю вас к изучению QSSL документации для получения более подробных сведений об этих функциях.
К функциям базового уровня относятся:
resmgr_msgreadv() и resmgr_msgread()
Считывают данных из адресного пространства клиента при помощи обмена сообщениями.
resmgr_msgwritev() и resmgr_msgwrite()
Записывают данные в адресное пространство клиента при помощи обмена сообщениями.
resmgr_open_bind()
Связывает контекст с запросом на установление соединения, поступившим от соответствующей клиентской функции. Этот контекст далее будет использоваться функциями ввода/вывода.
resmgr_attach()
Создает канал и связывает воедино имя пути, дескриптор диспетчера, функции установления соединения, функции ввода/вывода и другие параметры. Посылает сообщение администратору процессов для регистрации имени пути (префикса).
resmgr_detach()
Противоположна функции resmgr_attach(). Уничтожает связь между именем пути и администратором ресурса.
pulse_attach()
Связывает код импульса с функцией. Поскольку приема сообщений реализуется библиотекой, это удобный способ «перехватывать управление» для обработки импульсов.
pulse_detach()
Отвязывает код импульса от функции.
В дополнение к функциям, перечисленным выше, есть также множество функций, посвященных интерфейсу диспетчеризации.
Одна функция из вышеупомянутого списка заслуживает особого упоминания — функция resmgr_open_bind(). Данная функция, когда приходит сообщение установления соединения (обычно это происходит в результате клиентского вызова open() или fopen()), создает некую контекстную информацию, чтобы она была готов к моменту прихода сообщения ввода/вывода. Почему ее не быт администраторе /dev/null? Потому что POSIX-функции обработки сообщений, принятые по умолчанию, сами вызывают для нас эту функцию. Если бы мы обрабатывали все сообщения самостоятельно, нам, конечно, пришлось бы вызвать данную функцию.
Функция resmgr_open_bind() не только формирует контекстный блок для последующих сообщений ввода/вывода, но также инициализирует и другие структуры данных, используемые непосредственно библиотекой администратора ресурсов.
Остальные функции из вышеупомянутого списка достаточно интуитивны, так что отложим их обсуждение до тех пор, пока не дойдем до их явного применения.
Уровень POSIXВторой уровень библиотеки администратора ресурсов является POSIX-уровнем. Как и в случае с базовым уровнем, вы могли бы написать администратор ресурсов и без использования этих функций, но это отняло бы уйму трудов! Прежде чем обсуждать функции уровня POSIX в подробностях, мы должны рассмотреть ряд структур данных базового уровня, приходящие от клиентуры сообщения, а также общую структуру и сферу ответственности администратора ресурсов.
Написание администратора ресурсов
Теперь, когда мы знаем основы, — как выглядит мир глазами клиента, в каком цвете видит все администратор ресурсов, и что из себя представляют оба уровня библиотеки — пришло время сконцентрироваться на деталях.
В этом разделе мы рассмотрим следующие темы:
• структуры данных;
• структуру администратора ресурсов;
• структуры данных POSIX-уровня;
• подпрограммы обработки сообщений;
• и, конечно, множество примеров.
Постарайтесь запомнить приведенную ниже «большую картинку» — на ней изображено практически все, что имеет отношение к администратору ресурсов:
Архитектура администратора ресурсов — общая схема.