Этап 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() может и не передавать никаких данных— вы можете использовать ее просто для того, чтобы разблокировать клиента.
Читать дальше