Роб Кёртен - Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
Обработка команды «get» («получить») на этапе 4 во многом аналогична (по части приведения типов), кроме того, что на этот раз мы записываем данные в структуру вместо считывания из нее. Заметьте, что мы также присваиваем переменной nbytes число байт, которые мы хотим возвратить клиенту. В случае более сложного доступа к данным вы должны были бы возвратить размер области данных (т.е. если бы эта область была бы структурой, вам нужно было бы возвратить ее размер).
Этап 5Наконец, для возврата данных клиенту мы должны вспомнить, что клиент ожидает не только возвращаемые данные (если таковые имеются), но и заголовочную структуру, за которой идут эти данные. Поэтому на этом этапе мы обнуляем заголовочную структуру и устанавливаем число байт (поле nbytes) в число байт, которые мы намерены возвратить (вспомните, мы обнуляли это значение ранее). Затем мы создаем одноэлементный вектор ввода/ вывода с указателем на заголовок и расширяем размер заголовка на число возвращаемых байт. В конце мы просто сообщаем библиотеке администратора ресурсов, что мы возвращаем клиенту одноэлементный вектор ввода/вывода.
Важное замечаниеВспомните рассуждения про следующую за заголовком область данных из примера io_write(), приведенного выше. Мы утверждали, что байты, расположенные сразу после заголовка, могут как быть полноценными, так и нет (то есть возможны случаи, когда область данных со стороны клиента была считана лишь частично) — в зависимости от того, сколько данных считала библиотека администратора ресурсов. Затем мы говорили о том, что было бы неэффективно пытаться «сэкономить» лишнюю операцию обмена сообщениями и «повторно использовать» область данных. Однако, в случае с devctl() все обстоит несколько иначе, особенно если количество передаваемых данных достаточно невелико (как было и в наших примерах). Здесь у нас есть неплохой шанс того, что данные от клиента были-таки считаны в область данных целиком, и тогда повторное их считывание будет напрасной тратой сил. Узнать, сколько у вас доступно пространства, очень просто: поле size («размер») структуры ctp содержит число байт, доступных для вас, начиная с параметра msg. Размер доступной области данных, расположенной за буфером сообщений, вычисляется как разность между размером буфера сообщений и полем size структуры ctp:
data_area_size = ctp->size - sizeof(*msg);
Отметим, что этот размер будет действителен также и в случае возврата данных клиенту (как при команде DCMD_AUDIO_GET_SAMPLE_RATE).
Для всего, что превосходит по размеру выделенную область, вам придется получать данные от клиента так же, как мы это делали в примере с io_write() (см. выше), а также выделить буфер для возврата данных клиенту.
Дополнительно
Теперь, после того как мы овладели «основами» построения администраторов ресурсов, пришло время рассмотреть более сложные вопросы. К ним относятся:
• расширение OCB;
• расширение атрибутной записи;
• блокирование в пределах администратора ресурсов;
• возврат элементов каталога.
Расширение OCB
В ряде случаев у вас может возникнуть необходимость расширения OCB. Процедура эта является относительно безболезненной. Обычно OCB расширяют дополнительными флагами, характеризующими каждый конкретный open(). Один такой флаг можно было бы использовать с обработчиком io_unblock() для кэширования значения флага ядра _NTO_MI_UNBLOCK_REQ (подробнее см. параграф «Применение флага _NTO_MI_UNBLOCK_REQ» в главе «Обмен сообщениями»).
Для расширения блока OCB вам нужно будет обеспечить две дополнительных функции: одну для выделения OCB, и одну — для его освобождения. Затем вы должны будете привязать эти две функции к записи точки монтирования. (Да-да, совершенно верно — вам понадобится запись точки монтирования, даже если только для этого.) И наконец, вы должны будете определить ваш собственный тип OCB, чтобы все прототипы в программе были корректны.
Давайте рассмотрим сначала описание типа OCB, а затем уже поглядим, как переопределяются функции:
#define IOFUNC_OCB_T struct my_ocb
#include <sys/iofunc.h>
Это сообщает включаемому файлу <sys/iofunc.h>, что именованная константа IOFUNC_OCB_T теперь указывает на вашу новую усовершенствованную структуру OCB.
Очень важно иметь в виду, что ваш «расширенный» OCB должен содержать «стандартный» OCB в качестве своего первого элемента! Это так, потому что вспомогательная библиотека POSIX везде передает указатель на то, что она считает стандартным OCB — о вашем расширенном OCB ей ничего не известно, так что первый элемент данных, расположенный по этому указателю, должен соответствовать стандартному OCB.
Вот наш расширенный OCB:
typedef struct my_ocb {
iofunc_ocb_t normal_ocb;
int my_extra_flags;
...
} my_ocb_t;
А вот код, иллюстрирующий, как переопределяются функции выделения и освобождения OCB в записи точки монтирования:
// Декларации
iofunc_mount_t mount;
iofunc_funcs_t mount_funcs;
// Задать в записи точки монтирования
// наши функции выделения/освобождения
// _IOFUNC_NFUNCS взята из .h-файла
mount_funcs.nfuncs = _IOFUNC_NFUNCS;
// Новая функция выделения OCB
mount_funcs.ocb_calloc = my_ocb_calloc;
// Новая функция освобождения OCB
mount_funcs.ocb_free = my_ocb_free;
// Настроить запись точки монтирования
memset(&mount, 0, sizeof(mount));
После этого остается только привязать запись точки монтирования к атрибутной записи:
...
attr.mount = &mount;
Функции my_ocb_calloc() и my_ocb_free() отвечают за выделение обнуленного расширенного OCB и освобождения OCB, соответственно. Вот их прототипы:
IOFUNC_OCB_T* my_ocb_calloc(resmgr_context_t *ctp,
IOFUNC_ATTR_T *attr);
void my_ocb_free(IOFUNC_OCB_T *ocb);
Это означает, что функции my_ocb_calloc() передаются одновременно и внутренний контекст администратора ресурсов, и атрибутная запись. Функция отвечает за возврат обнуленного OCB. Функция my_ocb_free() получает OCB и отвечает за освобождение выделенной под него памяти.
Для этих двух функций имеются два интересных применения (которые ничем не связаны с выполнением расширения блока OCB):
• контроль распределения/освобождения блока OCB;
• обеспечение более эффективного распределения/ освобождения
Контроль за OCBВ этом случае вы можете просто «подключиться» к функциям распределения/освобождения и контролировать использование OCB (например, вам может быть необходимо ограничить суммарное количество OCB). Это может оказаться полезным, если вы не перехватываете функцию io_open(), но создание (и, возможно, удаление) OCB все-таки хотите контролировать.
Более эффективное распределениеДругое применение для переопределения встроенных библиотечных функций распределения/освобождения OCB может заключаться в том, что вы можете захотеть хранить OCB в свободном списке вместо использования библиотечных calloc() и free(). Если вы распределяете и освобождаете OCB с большой частотой, это может оказаться более эффективно.
Расширение атрибутной записи
Вы можете захотеть расширить атрибутную запись в случаях, когда вам необходимо хранить дополнительную информацию об устройствах. Поскольку атрибутные записи создаются «по каждому устройству», это означает, что любая дополнительная информация, которую вы сохраните там, будет доступна для всех OCB, относящихся к этому устройству (поскольку OCB содержит указатель на атрибутную запись). В расширенных атрибутных записях часто хранятся такие параметры как скорость передачи данных по последовательному каналу, и т.п.
Расширять атрибутную запись намного проще, чем OCB, потому что атрибутные записи в любом случае распределяются и освобождаются вашим кодом.
Вам нужно будет выполнить тот же трюк с переопределением атрибутной записи в заголовочных файлах, как мы это делали ранее при расширении OCB:
#define IOFUNC_ATTR_T struct my_attr
#include <sys/iofunc.h>
Затем вы фактически определяете содержимое ваших расширенных атрибутных записей. Отметьте, что расширенная атрибутная запись должна включать в себя стандартную атрибутную запись первым элементом — аналогично случаю с расширением OCB (и по тем же самым причинам).
Блокирование в пределах администратора ресурсов