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

We use cookies. Read the Privacy and Cookie Policy

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

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

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

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

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

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

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

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

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

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

Обработка EOF

Особым случаем согласования размеров областей данных является обработка EOF для строки фиксированной длины. Как только клиент считал заключительный символ « », дальнейшие его попытки считать данные должны возвращать 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 = "Здравствуй, мир! ";

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()) представляет собой число реально прочитанных байт — это общеизвестное соглашение.

Этап 6

Поскольку мы возвращаем данные от устройства, мы знаем, что к устройству производился доступ. Мы устанавливаем биты IOFUNC_ATTR_ATIME и IOFUNC_ATTR_DIRTY_TIME в поле flags атрибутной записи. Это служит напоминанием для функции io_stat() о том, что время доступа стало недействительным, и перед выполнением ответа его следует скорректировать по системным часам. Если бы нам очень хотелось, мы могли бы записать текущее время в поле atime атрибутной записи и сбросить флаг IOFUNC_ATTR_DIRTY_TIME; однако, это было бы не очень-то эффективно, поскольку мы предполагаем получить от клиента значительно большее запросов типа read(), чем запросов типа stat(). Впрочем, ваши условия могут диктовать иначе.

Так какое же время видит клиент, когда он вызывает-таки функцию stat()? Функция iofunc_stat_default(), предоставляемая библиотекой администратора ресурсов, посмотрит на поле flags атрибутной записи, чтобы проверить, являются времена доступа (поля atime, ctime и mtime) корректными или нет. Если нет (как это было бы после вызова io_read() с возвратом данных), iofunc_stat_default() устанавливает нужные из них в значение текущего времени.

Этап 7

Теперь мы увеличиваем смещение lseek() на число возвращенных клиенту байт, но делаем это только в том случае, если не обрабатываем модификатор _IO_XTYPE_OFFSET. Это гарантирует, что в случае отсутствия флага _IO_XTYPE_OFFSET, если клиент вызовет функцию lseek() для определения текущей позиции, или (более важный случай) если клиент вызовет read() для чтения еще нескольких байт, смещение в ресурсе будет корректным. Если _IO_XTYPE_OFFSET установлен, мы оставляем содержащееся в ocb смещение в покое.

Этап 8

Сопоставьте этот этап с этапом 6. Здесь мы только разблокируем клиента и не выполняем больше никаких действий. Также обратите внимание, что функции MsgReply() не передается никакой области данных, потому что в этом случае данные мы не возвращаем.

Этап 9

И наконец, на этапе 9 мы выполняем действия, не зависящие от того, возвращаем мы данные клиенту или нет. Поскольку мы уже сами разблокировали клиента при помощи MsgReply(), мы, конечно же, не хотим, чтобы это попыталась сделать еще и библиотека администратора ресурсов. Поэтому мы сообщаем ей, что мы уже сделали это сами, возвратом _RESMGR_NOREPLY.

Эффективное применение других функций обмена сообщениями

Как вы помните из главы «Обмен сообщениями», мы упоминали еще несколько функций обмена сообщениями, а именно — функции MsgWrite(), MsgWritev() и MsgReplyv(). Повод, в связи с которым я снова упоминаю здесь эти функции, состоит в том, что ваша функция io_read() может быть превосходным местом для их применения. В простом примере, показанном выше, мы возвращали непрерывный массив данных из постоянного места в памяти. В реальной же жизни вам может понадобиться возвратить, скажем, множество фрагментов данных из различных выделенных вами буферов. Классическим примером такого случая является циклический буфер, который часто применяется, например, в драйверах последовательных устройств. Часть данных может быть размещена в конце буфера, другая часть — в начале. В этом случае для возврата обеих частей данных вам понадобилось бы передать MsgReplyv() двухэлементный вектор ввода/вывода (IOV), где первый элемент содержал бы адрес (и длину) «нижней» части данных, а второй — адрес (и длину) «верхней» части. Или же, если вы ожидаете прибытия данных частями, вы могли бы вместо этого использовать функции MsgWrite() или MsgWritev() для записи данных в адресное пространство клиента по мере их поступления, а затем выдать заключительный вызов MsgReply() или MsgReplyv(), чтобы разблокировать клиента. Как мы уже показали выше, функция MsgReply() может и не передавать никаких данных— вы можете использовать ее просто для того, чтобы разблокировать клиента.