Структуры данных
Структуры данных
Первое, в чем следует разобраться, — это структуры данных, которые управляют работой библиотеки:
• управляющая структура resmgr_attr_t
• таблица функций установления соединения resmgr_connect_funcs_t
• таблица функций ввода-вывода resmgr_io_funcs_t и еще одна внутренняя структура данных библиотеки:
• внутренний блок контекста resmgr_context_t
Позже мы рассмотрим такие типы данных как блок открытого контекста (OCB), атрибутную запись (attributes structure) и запись точки монтирования (mount structure), которые используются POSIX-уровнем библиотеки.
Управляющая структура resmgr_attr_t
Управляющая структура (типа resmgr_attr_t) передается функции resmgr_start(), которая несколько ее элементов и отвечает за основной цикл приема сообщений.
Управляющая структура (взято из <sys/dispatch.h>) содержит следующее:
typedef struct _resmgr_attr {
unsigned flags;
unsigned nparts_max;
unsigned msg_max_size;
int (*other_func)(resmgr_context_t *ctp, void *msg);
} resmgr_attr_t;
Обработчик сообщений other_func
Вообще говоря, использования этого элемента следует избегать. Этот элемент, если не равен NULL, указывает на подпрограмму, которая должна быть вызвана, если принято сообщение, не распознанное библиотекой. Хоть это и можно было бы использовать для реализации «нестандартных» сообщений, но это нехорошая практика (применяйте либо обработчики _IO_DEVCTL, либо _IO_MSG — см. ниже). Если вы хотите обрабатывать входящие импульсы, рекомендую для этого применять функцию pulse_attach().
Так что оставьте у этого элемента значение NULL.
Параметры, задающие размеры структур данных
Эти два параметра используются для управления размерами областей памяти, используемых при обмене сообщениями.
Параметр nparts_max управляет размером динамически выделяемого вектора ввода/вывода (элемент iov в структуре типа resmgr_context_t — контекстном блоке библиотеки администратора ресурсов, см. ниже). Обычно этот параметр подстраивают, когда некоторые из функций-обработчиков возвращают более чем одноэлементный вектор ввода-вывода (IOV). Отметим, что этот параметр применяется только к исходящим сообщениям на поступающие сообщения не влияет.
Параметр msg_max_size управляет размером буферного пространства, которое библиотека администратора ресурсов должна выделить под входящее сообщение. Библиотека администратора ресурсов установит этот параметр в значение как минимум соответствующее наибольшему заголовку принимаемого сообщения. Это гарантирует, что когда будет вызвана функция- обработчик, ей будет передан полный заголовок сообщения. Отметим, однако, что присутствие в буфере следующих за заголовком данных (если таковые имеются) не гарантируется, даже если параметр msg_max_size задан достаточно большим. (Размеры буферов обсуждаются в параграфе «Внутренний контекстный блок resmgr_context_t», см. ниже).
Параметр flags
Этот параметр дает библиотеке администратора ресурсов дополнительную информацию. В нашем случае мы передадим просто нуль (0). Другие значения этого параметра можно найти в справочном руководстве по Си-библиотеке, в разделе, посвященном функции resmgr_attach().
Таблица функций установления соединения resmgr_connect_funcs_t
Когда библиотека администратора ресурсов принимает сообщение, она проверяет тип сообщения и смотрит, что можно сделать. Базовый уровень библиотеки содержит две таблицы, которые определяют это поведение. Это таблица типа resmgr_connect_funcs_t, которая содержит список обработчиков сообщений установления соединения, а также таблица типа геsmgr_io_funсs_t, которая содержит аналогичный список обработчиков сообщений ввода/вывода — ее мы рассмотрим несколько позже.
Когда придет время заполнить таблицы функций установления соединения и ввода/вывода, рекомендуется сначала воспользоваться функцией iofunc_func_init(), чтобы инициализировать таблицы функциями по умолчанию, определенными на уровне POSIX. Тогда, если вам потребуется заменить обработчик какого-либо сообщения, вы просто подставляете вместо POSIX-обработчика по умолчанию свою собственную функцию. Мы увидим это в разделе «Подстановка своих собственных функций». А сейчас давайте рассмотрим собственно таблицу функций установления соединения (взято из <sys/resmgr.h>):
typedef struct _resmgr_connect_funcs {
unsigned nfuncs;
int (*open)(ctp, io_open_t *msg, handle, void *extra);
int (*unlink)(ctp, io_unlink_t *msg, handle,
void *reserved);
int (*rename)(ctp, io_rename_t *msg, handle,
io_rename_extra_t *extra);
int (*mknod)(ctp, io_mknod_t *msg, handle,
void *reserved);
int (*readlink)(ctp, io_readlink_t *msg, handle,
void *reserved);
int (*link)(ctp, io_link_t *msg, handle,
io_link_extra_t *extra);
int (*unblock)(ctp, io_pulse_t *msg, handle,
void *reserved);
int (*mount) (ctp, io_mount_t *msg, handle,
io_mount_extra_t *extra);
} resmgr_connect_funcs_t;
Заметьте, что я сократил прототипы, опустив тип первого параметра, ctp (resmgr_context_t*), и третьего, handle (RESMGR_HANDLE_T).
Полный прототип для, например, функции open() в действительности имеет вид:
int (*open)(resmgr_context_t *ctp, io_open_t *msg,
RESMGR_HANDLE_T *handle, void *extra);
Первый элемент структуры (nfuncs) указывает, насколько она велика (то есть сколько в ней содержится элементов). Для приведенной выше структуры он должен быть равен 8, поскольку элементов в ней восемь (от open() до mount()). Этот элемент нужен главным образом для того, чтобы позволить QSSL обновлять данную библиотеку без каких бы то ни было вредных последствий для вашего кода. Предположим, к примеру, что вы компилировали свой код со значением 8, а затем QSSL обновила библиотеку, и параметр стал равен 9. Библиотека могла бы себе сказать: «Ага! Пользователь библиотеки был скомпилирован с расчетом на 8 функций, а их у нас теперь 9. Надо бы назначить 9-й функции обработчик по умолчанию.» Текущее значение параметра nfuncs хранится в заголовочном файле <sys/resmgr.h> в виде именованной константы _RESMGR_CONNECT_NFUNCS. Используйте эту константу при заполнении таблицы функций установления соединения вручную (хотя лучше всего применять для этого функцию iofunc_func_init()).
Отметим, что формат у всех прототипов один и тот же. Первый параметр, ctp, указывает на структуру resmgr_context_t. Это внутренний контекстный блок, используемый библиотекой администратора ресурсов и который изменять не следует (за исключением одного поля, к обсуждению которого мы еще вернемся).
Второй параметр всегда указывает на сообщение. Поскольку функции в таблице предназначены для обработки различных типов сообщений, тип второго параметра в прототипе соответствует типу сообщения, которое данная функция должна обрабатывать.
Третий параметр — структура типа RESMGR_HANDLE_T, называемая дескриптором (handle). Она используется для идентификации устройства, которому предназначалось сообщение. Мы тоже рассмотрим ее позже, когда будем говорить об атрибутной записи.
И, наконец, последний параметр является «резервным», или «дополнительным», и используется для функций, которым необходимы какие-либо дополнительные данные. Мы продемонстрируем применение параметра extra по назначению в обсуждении функций-обработчиков сообщений.
Таблица функций ввода/вывода resmgr_io_funcs_t
Таблица функций ввода/вывода подобна таблице функций установления соединения. Вот она (взято из <sys/resmgr.h>):
typedef struct _resmgr_io_funcs {
unsigned nfuncs;
int (*read)(ctp, io_read_t *msg, ocb);
int (*write)(ctp, io_write_t *msg, ocb);
int (*close_ocb)(ctp, void *reserved, ocb);
int (*stat)(ctp, io_stat_t *msg, ocb);
int (*notify)(ctp, io_notify_t *msg, ocb);
int (*devctl)(ctp, io_devctl_t *msg, ocb);
int (*unblock)(ctp, io_pulse_t *msg, ocb);
int (*pathconf)(ctp, io_pathconf_t *msg, ocb);
int (*lseek)(ctp, io_lseek_t *msg, ocb);
int (*chmod)(ctp, io_chmod_t *msg, ocb);
int (*chown)(ctp, io_chown_t *msg, ocb);
int (*utime)(ctp, io_utime_t *msg, ocb);
int (*openfd)(ctp, io_openfd_t *msg, ocb);
int (*fdinfo)(ctp, io_fdinfo_t *msg, ocb);
int (*lock)(ctp, io_lock_t *msg, ocb);
int (*space)(ctp, io_space_t *msg, ocb);
int (*shutdown)(ctp, io_shutdown_t *msg, ocb);
int (*mmap)(ctp, io_mmap_t *msg, ocb);
int (*msg)(ctp, io_msg_t *msg, ocb);
int (*umount)(ctp, void *msg, ocb);
int (*dup)(ctp, io_dup_t *msg, ocb);
int (*close_dup)(ctp, io_close_t *msg, ocb);
int (*lock_ocb)(ctp, void *reserved, ocb);
int (*unlock_ocb)(ctp, void *reserved, ocb);
int (*sync)(ctp, io_sync_t *msg, ocb);
} resmgr_io_funcs_t;
В этой структуре я тоже сократил прототипы, опустив тип элемента ctp (resmgr_context_t*) и тип последнего элемента, ocb (RESMGR_OCB_T*). Полный прототип, например, для функции read() в действительности имеет вид:
int (*read)(resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb);
Самый первый элемент структуры (nfuncs) указывает, насколько она велика (то есть сколько элементов она содержит). Текущее значение этого элемента содержится в константе _RESMGR_IO_NFUNCS.
Отметим, что списки параметров в таблице функций ввода/вывода также довольно однообразны. Первый параметр — ctp, второй параметр — msg, как и у обработчиков из таблицы функций установления соединения.
Третий параметр, однако, отличается. Этот параметр называется ocb, что расшифровывается как «Open Context Block» — «блок открытого контекста». Этот блок содержит контекст, созданный обработчиком сообщения установления соединения (например, в результате клиентского запроса open()) и доступный функциям ввода/вывода.
Как уже упоминалось ранее, когда придет время заполнять таблицы функций, рекомендуется пользоваться для этого функцией iofunc_func_init(), чтобы сначала загрузить таблицы POSIX-обработчиками по умолчанию. Если же вам будет нужно переопределить обработчики сообщений определенного типа, вы сможете просто заменить POSIX-обработчики по умолчанию на свои собственные. Мы рассмотрим это в разделе «Подстановка своих собственных функций».
Внутренний контекстный блок resmgr_context_t
И, наконец, еще одна структура данных используется базовым уровнем библиотеки, чтобы отслеживать кое-какую информацию для себя. Вам не следует изменять содержимое этой структуры, за исключением одного элемента — вектора ввода/вывода iov.
Вот эта структура данных (взято из <sys/resmgr.h>):
typedef struct _resmgr_context {
int rcvid;
struct _msg_info info;
resmgr_iomsgs_t *msg;
struct _resmgr_ctrl *ctrl;
int id;
int status;
int offset;
int size;
iov_t iov[1];
} resmgr_context_t;
Как и в случае с другими структурами данных, я позволил себе опустить зарезервированные поля.
Давайте взглянем на ее содержимое.
rcvid Идентификатор отправителя, полученный от MsgReceivev(). Указывает, кому вы должны ответить (если вы намерены отвечать самостоятельно). info Содержит информационную структуру, возвращаемую функцией MsgReceivev() в основном цикле приема сообщений библиотеки администратора ресурсов. Полезна для получения информации о клиенте, включая дескриптор узла, идентификатор процесса (PID), идентификатор потока и т.д. Подробнее см. документацию по функции MsgReceivev(). msg Указатель на объединение (union) всех возможных типов сообщений. Практически бесполезен, потому что каждая из ваших функций-обработчиков получает соответствующий элемент объединения вторым параметром. ctrl Указатель на управляющую структуру, которую вы передали в самом начале. Опять же, для вас этот параметр не очень полезен, но зато полезен для библиотеки администратора ресурсов. id Идентификатор точки монтирования, которой предназначалось сообщение. Когда вы вызывали resmgr_attach(), она вернула вам небольшой целочисленный идентификатор. Это и есть значение id. Отметим, что вы вероятнее всего никогда не будете использовать этот параметр самостоятельно, а будете полагаться вместо этого на атрибутную запись, передаваемую вам обработчиком io_open(). status Сюда ваша функция-обработчик помещает результат выполнения операции. Отметим, что вы должны всегда использовать макрос _RESMGR_STATUS() для заполнения этого поля. Например, если вы обрабатываете сообщение установления соединения от функции open(), причем ваш администратор ресурса предоставляет доступ «только для чтения», а клиент хотел открыть ресурс на запись, вы возвратите клиенту через errno код EROFS при помощи (обычно) _RESMGR_STATUS(ctp, EROFS). offset Текущее смещение (в байтах) в клиентском буфере сообщений. Имеет смысл для базового уровня библиотеки только при чтении составных сообщений функцией resmgr_msgreadv(). size Этот параметр говорит, сколько байт в буфере сообщения, переданном вашей функции-обработчику, являются достоверными. Это важная цифра, поскольку она указывает на то, требуется ли читать дополнительные данные от клиента (например, если не все данные были считаны базовым уровнем библиотеки), и надо ли выделить память для ответа клиенту (например, для ответа на запрос read()). (Отметим, что в версии 2.00 есть ошибка, из-за которой это поле не заполняется в случае несоставного сообщения установления соединения. Все остальные сообщения обрабатываются корректно. Обходной путь здесь (и только здесь!) заключается в использовании параметра msglen структуры info.) iov Таблица векторов ввода/вывода, в которую вы можете записывать возвращаемые значения, если это необходимо. Например, когда клиент вызывает read(), и у вас вызывается соответствующий обработчик read(), вам может потребоваться возвратить клиенту данные. Можно задать эти данные при помощи массива iov и возвратить что-нибудь типа _RESMGR_NPARTS(2), указав тем самым (в нашем случае), векторы iov[0] и iov[1] содержат данные для клиента. Заметьте, что массив iov определен как одноэлементный. Однако, заметьте также, что он очень удобно расположен в конце структуры. Фактическое число элементов в массиве iov определяете вы сами, когда присваиваете значение полю nparts_max вышеупомянутой управляющей структуры (см. параграф «Управляющая структура resmgr_attr_t»).