Выбрать главу

Как правило, не следует регистрировать больше чем несколько дюжин отдельных префиксов, потому что поиск по дереву является линейным. Число 256 портов определенно больше. В таких случаях что мультипортовый администратор ресурсов должен сделать, так это зарегистрировать каталогоподобный префикс, например, /dev/multiport. Это займет только один элемент в дереве имен путей. Клиент открывает последовательный порт, скажем, порт 57:

fp = fopen("/dev/multiport/57", "w");

Администратор процессов разрешает это в четверку ND/PID/CHID/handle для мультипортового администратора; решать, насколько корректен при этом остаток имени («57») — это уже дело самого администратора ресурса. В этом примере, предположив, что часть имени пути после точки монтирования хранится в переменной path, администратор ресурса мог бы выполнить проверку очень простым способом:

devnum = atoi(path);

if ((devnum <= 0) || (devnum >= 256)) {

 // Неправильный номер устройства

} else {

 // Правильный номер устройства

}

Этот будет однозначно быстрее, чем поиск, выполняемый администратором процессов, — хотя бы потому что администратор процессов по сути своей намного более универсален, чем наш администратор ресурса.

Обработка сообщений

Как только мы зарегистрировали один или более префиксов, мы должны быть готовы принимать сообщения от клиентов. Это делается «обычным» способом с помощью функции MsgReceive(). Существуют менее 30 четко определенных типов сообщений, которые администратор ресурса должен быть способен обработать. Однако, для упрощения обсуждения и реализации их условно делят на две группы:

Сообщения установления соединения (connect messages)

 Всегда содержат имя пути; либо являются однократными, либо устанавливают контекст для последующих сообщений ввода/вывода.

Сообщения ввода/вывода (I/O messages)

 Всегда базируются на сообщениях установления соединения; выполняют всю последующую работу.

Сообщения установления соединения

Сообщения установления соединения всегда содержат имя пути. Прекрасным примером функции, генерирующей сообщение установления соединения, является уже не раз упомянутая нами функция open(). В этом случае обработчик (handle) сообщения установления соединении устанавливает контекст для последующих сообщений ввода/вывода. (В конце-то концов, после open() мы все-таки собираемся делать что-то наподобие read().).

Примером «однократного» сообщения установления соединения может быть сообщение, сгенерированное в результате вызова rename(). Здесь никакого контекста не предполагается — обработчик в администраторе ресурса изменяет имя указанного файла на новое, и все.

Сообщения ввода/вывода

Сообщения ввода/вывода возникают только после соответствующего сообщения установления соединения и ссылаются на установленный им контекст. Как уже упоминалось ранее при обсуждении сообщений установления соединения, идеальный пример — вызов функции open(), за которым следует read().

На самом деле групп сообщений три

Кроме сообщений об установлении соединения и сообщений ввода/вывода, есть еще и «другие» сообщения, которые администратор ресурсов может принимать и обрабатывать. Но поскольку они не являются в полной мере «административными», покамест отложим их обсуждение и вернемся к ним позже.

Библиотека администратора ресурсов

Прежде чем лезть в глубины организации администраторов ресурсов, познакомимся сначала с библиотекой администратора ресурсов, разработанной QSSL. Отметим, что в действительности эта «библиотека» состоит из нескольких четко различимых частей:

• функции пула потоков (мы обсуждали их в главе «Процессы и потоки», в параграфе «Пулы потоков»);

• интерфейс диспетчеризации;

• функции администратора ресурсов;

• вспомогательные функции POSIX-библиотеки.

При том, что можно было бы, конечно, писать администраторы ресурсов «с нуля» (как это делалось в QNX4), эта овчинка часто не стоит такой выделки.

Просто для демонстрации практичности библиотечного подхода — вот код однопоточной версии администратора «/dev/null»:

/*

 * resmgr1.c

 *

 * /dev/null на основе библиотеки администратора ресурсов

*/

#include <stdio.h>

#include <stdlib.h>

#include <stddef.h>

#include <sys/iofunc.h>

#include <sys/dispatch.h>

int main(int argc, char **argv) {

 dispatch_t             *dpp;

 resmgr_attr_t          resmgr_attr;

 resmgr_context_t       *ctp;

 resmgr_connect_funcs_t connect_func;

 resmgr_io_funcs_t      io_func;

 iofunc_attr_t          attr;

 // Создать структуру диспетчеризации

 if ((dpp = dispatch_create()) == NULL) {

  perror("Ошибка dispatch_create\n");

  exit(EXIT_FAILURE);

 }

 // Инициализировать структуры данных

 memset(&resmgr_attr, 0, sizeof(resmgr_attr));

 resmgr_attr.nparts_max = 1;

 resmgr_attr.msg_max_size = 2048;

 // Назначить вызовам обработчики по умолчанию

 iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_func,

  _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_attach\n");

  exit(EXIT_FAILURE);

 }

 ctp = resmgr_context_alloc(dpp);

 // Ждать сообщений в вечном цикле

 while (1) (

  if ((ctp = resmgr_block(ctp)) == NULL) {

   perror("Ошибка resmgr_block\n");

   exit(EXIT_FAILURE);

  }

  resmgr_handler(ctp);

 }

}