Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
Шрифт:
Поскольку мы возвращаем данные от устройства, мы знаем, что к устройству производился доступ. Мы устанавливаем биты IOFUNC_ATTR_ATIME и IOFUNC_ATTR_DIRTY_TIME в поле flags атрибутной записи. Это служит напоминанием для функции io_stat о том, что время доступа стало недействительным, и перед выполнением ответа его следует скорректировать по системным часам. Если бы нам очень хотелось, мы могли бы записать текущее время в поле atime атрибутной записи и сбросить флаг IOFUNC_ATTR_DIRTY_TIME; однако, это было бы не очень-то эффективно, поскольку мы предполагаем получить от клиента значительно большее запросов типа read, чем запросов типа stat. Впрочем, ваши условия могут
Теперь мы увеличиваем смещение lseek на число возвращенных клиенту байт, но делаем это только в том случае, если не обрабатываем модификатор _IO_XTYPE_OFFSET. Это гарантирует, что в случае отсутствия флага _IO_XTYPE_OFFSET, если клиент вызовет функцию lseek для определения текущей позиции, или (более важный случай) если клиент вызовет read для чтения еще нескольких байт, смещение в ресурсе будет корректным. Если _IO_XTYPE_OFFSET установлен, мы оставляем содержащееся в ocb смещение в покое.
Сопоставьте этот этап с этапом 6. Здесь мы только разблокируем клиента и не выполняем больше никаких действий. Также обратите внимание, что функции MsgReply не передается никакой области данных, потому что в этом случае данные мы не возвращаем.
И наконец, на этапе 9 мы выполняем действия, не зависящие от того, возвращаем мы данные клиенту или нет. Поскольку мы уже сами разблокировали клиента при помощи MsgReply, мы, конечно же, не хотим, чтобы это попыталась сделать еще и библиотека администратора ресурсов. Поэтому мы сообщаем ей, что мы уже сделали это сами, возвратом _RESMGR_NOREPLY.
Как вы помните из главы «Обмен сообщениями», мы упоминали еще несколько функций обмена сообщениями, а именно — функции MsgWrite, MsgWritev и MsgReplyv. Повод, в связи с которым я снова упоминаю здесь эти функции, состоит в том, что ваша функция io_read может быть превосходным местом для их применения. В простом примере, показанном выше, мы возвращали непрерывный массив данных из постоянного места в памяти. В реальной же жизни вам может понадобиться возвратить, скажем, множество фрагментов данных из различных выделенных вами буферов. Классическим примером такого случая является циклический буфер, который часто применяется, например, в драйверах последовательных устройств. Часть данных может быть размещена в конце буфера, другая часть — в начале. В этом случае для возврата обеих частей данных вам понадобилось бы передать MsgReplyv двухэлементный вектор ввода/вывода (IOV), где первый элемент содержал бы адрес (и длину) «нижней» части данных, а второй — адрес (и длину) «верхней» части. Или же, если вы ожидаете прибытия данных частями, вы могли бы вместо этого использовать функции MsgWrite или MsgWritev для записи данных в адресное пространство клиента по мере их поступления, а затем выдать заключительный вызов MsgReply или MsgReplyv, чтобы разблокировать клиента. Как мы уже показали выше, функция MsgReply может и не передавать никаких данных— вы можете использовать ее просто для того, чтобы разблокировать клиента.
Простой
пример функции io_writeЭто был простой пример функции io_read; давайте теперь перейдем к функции io_write. Основной камень преткновения, связанный с io_write, — получить доступ к данным. Поскольку библиотека администратора ресурсов считывает лишь незначительную часть сообщения от клиента, переданные клиентом данные (они идут сразу после заголовка _IO_WRITE) могут быть приняты функцией io_write только частично. Простой пример — представьте себе клиента, записывающего один мегабайт. Библиотекой администратора ресурсов будут считаны только заголовок сообщения и несколько байт данных. Остальная часть мегабайта остается по-прежнему доступной на клиентской стороне — администратор ресурсов при желании может к ней обращаться.
Реально рассмотрения заслуживают только два случая:
• все содержимое сообщения клиентской функции write было считано библиотекой администратора ресурсов полностью; или
• этого не произошло.
Судьбоносное решение, однако, состоит в ответе на следующий вопрос: «Какие проблемы сопряжены с попыткой сохранить полученную с первым сообщением часть данных?» Ответ такой: овчинка не стоит выделки. Тому есть ряд причин:
• обмен сообщениями (операции копирования на уровне ядра) выполняется очень быстро;
• проверка, получены ли данные целиком или частично, влечет определенные накладные расходы;
• дополнительные накладные расходы связаны с попыткой «сохранения» первой, уже прибывшей, части данных, в свете того факта, что ожидаются дополнительные.
По-моему, первые два пункта говорят сами за себя. Третий же пункт заслуживает пояснения. Давайте предположим, что клиент переслал большую порцию данных, и мы приняли-таки решение о том, что было бы неплохо попробовать сохранить ту часть данных, которая уже получена. К сожалению, эта часть оказалось очень небольшой. Это означает, что вместо одного непрерывного массива байт у нас теперь будет большой кусок и маленький «довесок». Иными словами, только ради этого маленького кусочка нам придется значительно усложнять работу с данными, что может неприятно сказаться на эффективности кода. Это потенциальная головная боль, не делайте так!
Реальным ответом на поставленный вопрос будет просто заново считать данные в заранее подготовленные буферы. В нашем простом примере функции io_write я буду просто каждый раз выделять буфер при помощи malloc, считывать в него данные, а затем освобождать его функцией free. Разумеется, есть и куда более эффективные способы выделения буферов и управления ими!
Еще один тонкий момент в этом примере функции io_write — это обработка модификатора _IO_XTYPE_OFFSET (и связанных с этим данных; здесь она выполняется несколько иначе, чем в примере io_read).