Здесь мы дополняем администратор ресурсов нашими функциями. Эти функции представляют собой функции- обработчики сообщений, о которых мы только что говорили (например, 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);
off = xoffset->offset;
} else if (xtype = _IO_XTYPE_NONE) {
off = ocb->offset;
} else { // Неизвестный тип; игнорировать
return ENOSYS;
}
// 3) Сколько байт осталось?
nleft = ocb->attr->nbytes – off;
// 4) Сколько байт мы можем отдать клиенту?
nbytes = min(nleft, msg->i.nbytes);
// 5) Если возвращаем данные, отдать их клиенту
if (nbytes) {
MsgReply(ctp->rcvid, nbytes, data_string+off, nbytes);
// 6) Установить значение "atime" для POSIX stat()
ocb->attr->flags |=
IOFUNC_ATTR_ATIME | IOFUNC_ATTR_DIRTY_TIME;
// 7) Если индекс lseek() не равен _IO_XTYPE_OFFSET,
// увеличить его на число считанных байт
if (xtype == _IO_XTYPE_NONE) {
ocb->offset += nbytes;
}
} else {
// 8) Не возвращаем данные, просто разблокировать клиента
MsgReply(ctp->rcvid, EOK, null, 0);
}
// 9) Сказать библиотеке, что мы уже ответили сами
return _RESMGR_NOREPLY;
}
Этап 1
Здесь мы убедились, что клиентский вызов open() действительно запросил открытие устройства на чтение. Если бы клиент открыл устройство только на запись, а затем попытался выполнить чтение, это следовало бы расценивать как ошибку. В этом случае вспомогательная функция iofunc_read_verify() возвратила бы нам (затем мы — библиотеке, а библиотека — клиенту) EBADF, а не EOK.
Этап 2
Здесь мы проверили, указал ли клиент индивидуальное для данного сообщения переопределение типа (xtype-override) (например, потому что если мы открыли устройство в неблокирующем режиме, то это указало бы, что для данного конкретного запроса мы хотим задать блокирующее поведение). Отметим, что блокирующий аспект переопределенияа типа может быть отражён в последнем параметре функции iofunc_read_verify(), однако, поскольку мы приводим здесь упрощенный пример, мы передаем NULL, указывая этим, что этот вопрос нас не волнует.
Более важно, однако, посмотреть, как обрабатываются конкретные модификаторы xtype. Очень интересен, например, модификатор _IO_XTYPE_OFFSET, который, если присутствует, указывает на то, что принятое от клиента сообщение содержит смещение, и что операция чтения не должна изменять «текущую позицию файла» для данного файлового дескриптора (так делает, например, функция pread()). Если модификатор _IO_XTYPE_OFFSET не указан, то операция чтения может смело модифицировать «текущую позицию файла». Мы используем переменную хtype для сохранения xtype, содержавшегося в принятом сообщении, и переменную off для представления текущего смещения, которое мы должны будем использовать при обработке. Далее, на этапе 7, вы увидите еще кое-какие действия по обработке модификатора _IO_XTYPE_OFFSET.
Если присутствует иное переопределение xtype, чем _IO_XTYPE_OFFSET (и это не пустая команда _IO_XTYPE_NONE), мы отказываемся обрабатывать запрос и возвращаем ENOSYS. Это просто означает, что мы не знаем, как обрабатывать такую ситуацию, и поэтому возвращаем клиенту признак ошибки.
Этапы 3 и 4
Чтобы вычислить, сколько байт мы можем реально возвратить клиенту, мы выполняем этапы 3 и 4, в которых выясняется, сколько байт доступно у устройства (разность между полным объемом устройства, полученным из ocb->attr->nbytes, и текущим смещением в устройстве). Узнав, сколько байт осталось, мы выбираем наименьшее значение между размером этого остатка и количеством байт, которые клиент хочет прочитать. Например, у нас может остаться семь байт, а клиент захочет прочитать только два. В этом случае мы возвратим клиенту только два байта. И наоборот, если клиент захочет прочитать 4096 байт, а у нас осталось только семь, мы сможем возвратить ему только семь байт.
Этап 5
Теперь, вычислив, сколько байт мы намерены возвратить клиенту, нам нужно сделать ряд вещей в зависимости от того, возвращаем мы данные или нет. Если да, то мы просто отвечаем клиенту с данными сразу после проверки на этапе 5. Обратите внимание, что для возврата данных с корректного смещения мы используем data_string + off (смещение off вычисляется в зависимости от наличия переопределения типа). Отметьте также второй параметр функции MsgReply() — в документации он упоминается как «status» («код завершения»), но в этом случае мы используем его для возврата числа байт. Мы делаем так, потому что реализация клиентской функции read() знает, что значение, возвращаемое ее функцией MsgSendv() (а это, кстати, как раз и есть параметр status функции MsgReply()) представляет собой число реально прочитанных байт — это общеизвестное соглашение.