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

 pool_attr.increment = 1;

 pool_attr.maximum = 50;

 if ((tpp =

  thread_pool_create(&pool_attr, POOL_FLAG_EXIT_SELF))

   == NULL) {

  fprintf(stderr,

   "%s: Ошибка инициализации пула потоков\n",

  argv[0]);

  return (EXIT_FAILURE);

 }

 iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_func,

  _RESMGR_IO_NFUNCS, &io_func);

 iofunc_attr_init(&attr, S_IFNAM | 0777, 0, 0);

 // 2) Переопределить функции установления соединения

 // и функции ввода/вывода, как надо

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

 resmgr_attr.nparts_max = 1;

 resmgr_attr.msg_max_size = 2048;

 // 3) Замените «/dev/whatever» на нужный префикс

 if ((id =

  resmgr_attach(dpp, &resmgr_attr, "/dev/whatever",

   _FTYPE_ANY,

   0, &connect_func, &io_func, &attr)) == -1) {

  fprintf(stderr, "%s: Ошибка регистрации префикса\n",

   argv[0]);

  return (EXIT_FAILURE);

 }

 // Отсюда возврата не будет

 thread_pool_start(tpp);

}

Дополнительную информацию об интерфейсе диспетчеризации (т.е., о функции dispatch_create()), см. в справочном руководстве по Си-библиотеке (С Library Reference).

Этап 1

Здесь мы используем функции пула потоков для создания пула, который должен будет обслуживать сообщения в нашем администраторе ресурсов. Вообще говоря, я бы рекомендовал вам начать однопоточного администратора ресурсов, как мы это делали ранее в примере с администратором /dev/null. Как только базовая функциональность у вас заработает, вы сможете затем добавить многопоточность. Вам нужно будет задать параметры lo_water, hi_water, increment и maximum структуры pool_attr, как это было описано в главе «Процессы и потоки» в обсуждениях функций пула потоков.

Этап 2

Здесь мы дополняем администратор ресурсов нашими функциями. Эти функции представляют собой функции- обработчики сообщений, о которых мы только что говорили (например, io_read(), io_devctl(), и т.п.). Например, чтобы добавить свой собственный обработчиком для сообщения _IO_READ, указывающий на функцию my_io_read(), мы должны были бы добавить в программу такую строчку:

io_func.io_read = my_io_read;

Это переопределит элемент таблицы, инициализированный вызовом iofunc_func_init() и содержавший функцию POSIX-уровня по умолчанию, заменив его указателем на вашу функцию my_io_read().

Этап 3

Вы, вероятно, не захотите, чтобы ваш администратор ресурсов назывался /dev/whatever (букв. — «/dev/абы_что» — прим. ред.), так что вам придется выбрать для него соответствующее имя. Отметим, что привязка атрибутной записи (параметр attr) к регистрируемому префиксу осуществляется вызовом resmgr_attach() — если бы нам было надо, чтобы наш администратор обрабатывал несколько устройств, нам пришлось бы вызывать resmgr_attach() несколько раз, каждый раз с новой атрибутной записью, чтобы на этапе выполнения можно было отличить зарегистрированные префиксы друг от друга.

Простой пример функции io_read()

Чтобы проиллюстрировать, как ваш администратор ресурса мог бы возвращать клиенту данные, рассмотрим простейший администратор ресурса, который всегда возвращает одну и ту же строковую константу «Здравствуй, мир!\n». Даже в таком простом случае необходимо учесть ряд моментов, как-то:

• согласование размера клиентской области данных с количеством данных, подлежащих возврату;

• обработка EOF;

• поддерживание контекстной информации (индекс lseek());

• обновление POSIX-информации stat().

Учет размеров областей данных

В нашем случае администратор ресурсов возвращает фиксированную строку длиной в 17 байт, то есть размер доступных данных точно известен и постоянен. Эти аналогично случаю с дисковым файлом, доступным только для чтения и содержащим рассматриваемую строку. Единственное реальное отличие состоит в том, что этот «файл» обеспечивается в нашей программе строкой:

char *data_string = "Здравствуй, мир!\n";

С другой стороны, клиент может запросить чтение любого объема данных — один байт, 17 байт или более. Это должно отразиться на характеристиках вашей реализации io_read() ее умением согласовывать размер запрашиваемых клиентом данных с размером данных, имеющихся в наличии.

Обработка EOF

Особым случаем согласования размеров областей данных является обработка EOF для строки фиксированной длины. Как только клиент считал заключительный символ «\n», дальнейшие его попытки считать данные должны возвращать EOF.

Поддерживание контекстной информации

И «учет размеров областей данных», и «обработка EOF» требуют, чтобы в OCB, передаваемом вашей функции io_read(), поддерживалась контекстная информация — в частности, поле offset.

Обновление информации POSIX

И еще одно заключительное соображение: при чтении данных из ресурса должна обновляться POSIX-переменная времени доступа atime («access time» — «время доступа»). Это делается для того, чтобы клиентская функция stat() могла обнаружить, что к устройству кто-то обращался.

Собственно код

Ниже приведена программа, в которой учтены все вышеперечисленные моменты. Ниже мы ее последовательно проанализируем.

/*

 * io.read1.c

*/

#include <stdio.h>

#include <errno.h>

#include <sys/neutrino.h>

#include <sys/iofunc.h>

// наша строка с данными

char* data_string = "Здравствуй, мир!\n";

int io_read(resmgr_context_t* ctp, io_read_t* msg,

 iofunc_ocb_t* ocb) {

 int sts;

 int nbytes;

 int nleft;

 int off;

 int xtype;

 struct _xtype_offset* xoffset;

 // 1) Проверить, открыто ли устройство на чтение

 if ((sts ==

  iofunc_read_verify(ctp, msg, ocb, NULL)) != EOK) {

  return sts;

 }

 // 2) проверить и обработать переопределение XTYPE

 xtype = msg->i.xtype & _IO_XTYPE_MASK;

 if (xtype == _IO_XTYPE_OFFSET) {

  xoffset = (struct _xtype_offset*)(msg->i + 1);