Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
Шрифт:
Как вы видите, некоторые начальные действия идентичны таковым из примера функции io_read — функция iofunc_write_verify аналогична функции iofunc_read_verify, и проверка переопределения xtype выполняется точно также.
Здесь мы выполняем обработку переопределения xtype, в значительной степени аналогичную примеру с io_read — за исключением того, что смещение не сохраняется в поле структуры входящего сообщения. Причина этого состоит в том, что обычной практикой для определения начального адреса поступающих от клиента данных является использование размера структуры входящего сообщения. Мы предпринимаем дополнительные усилия, чтобы удостовериться, что смещение начала данных (doffset) в коде обработки xtype является корректным.
Здесь мы выделяем буфер, достаточный для размещения в нем данных. Число байт, которые клиент собирается записать, представлено нам в поле nbytes объединения msg, оно заполняется автоматически Си-библиотекой клиента в коде функции write. Отметим, что у нас недостаточно памяти для обработки запроса malloc, мы возвращаем клиенту ENOMEM, чтобы он знал, почему его запрос потерпел неудачу.
Здесь мы применяем вспомогательную функцию resmgr_msgread для считывания всего объема данных от клиента непосредственно в только что выделенный для этого буфер. В большинстве случаев здесь вполне сошла бы функция MsgRead, но в случаях, когда сообщение является частью составного сообщения, функция resmgr_msgread выполни для нас еще и соответствующие «магические» действия (о том, почему это надо, см. раздел «Составные сообщения»). Параметры функции resmgr_msgread довольно очевидны: мы передаем ей указатель на внутренний контекстный блок (ctp), буфер, в который мы хотим поместить данные (buffer), и число байт, которые мы хотим считать (поле nbytes
• мы могли бы взять произвольное смещение, чтобы считывать любые фрагменты данных в любом порядке, как нам заблагорассудится;
• мы могли бы использовать функцию resmgr_msgreadv (обратите внимание на символ «V» в названии) для считывания данных от клиента в вектор ввода/вывода, возможно, описывающий несколько разных буферов, подобно тому, как мы делали с буферами кэша при обсуждении файловых систем в главе «Обмен сообщениями».
Здесь вы можете делать с данными все, что вашей душе угодно — я, например, вызвал некую условную функцию process_data и передал ей буфер и его размер.
Критически важный этап! Упустить его из виду очень просто, но это неизбежно приведет к «утечкам памяти». Обратите также внимание, как мы позаботились об освобождении памяти в случае неудачи на этапе 3.
Здесь мы используем макрос _IO_SET_WRITE_NBYTES для сохранения числа записанных байт, которое затем будет передано назад клиенту в качестве значения, возвращаемого функцией write. Важно отметить, что вы должны возвратить фактическое число байт! От этого зависит судьба клиента.
Пришло время навести лоск для функций stat, lseek и последующих write, аналогично тому, как мы это делали в нашей io_read (и опять мы изменяем смещение в ocb только в том случае, если это не сообщение типа _IO_XTYPE_OFFSET). Однако, поскольку мы выполняем запись в устройство, мы используем при этом константу IOFUNC_ATTR_MTIME вместо константы IOFUNC_ATTR_ATIME. Флаг MTIME означает «время модификации» (modification time) — функция write определенно его изменяет.
Последний этап прост: мы возвращаем константу EOK, которая сообщает библиотеке администратора ресурсов, что она должна ответить клиенту. Здесь наша работа закончена. Библиотека администратора ресурсов использует в ответе данные о числе записанных байт, которые мы сохранили с помощью макроса IO_SET_WRITE_NBYTES, и разблокирует клиента. Клиентская функция write возвратит число байт, записанное нашим устройством.
Простой пример функции io_devctl
Клиентский вызов devctl формально определен так: