Чтение онлайн

ЖАНРЫ

Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform

Кёртен Роб

Шрифт:

 }

 // 1) Проверить и обработать переопределение

 XTYPE xtype = msg->i.xtype & _IO_XTYPE_MASK;

 if (xtype == _IO_XTYPE_OFFSET) {

xoffset = (struct _xtype_offset*)(&msg->i + 1);

start_data_offset = sizeof(msg->i) + sizeof(*xoffset);

off = xoffset->offset;

 } else if (xtype == _IO_XTYPE_NONE) {

off = ocb->offset;

start_data_offset = sizeof(msg->i);

 } else {

//
Неизвестный тип; игнорировать

return (ENOSYS);

 }

 // 2) Выделить достаточно большой буфер для данных

 nbytes = msg->i.nbytes;

 if ((buffer = malloc(nbytes)) == NULL) {

return (ENOMEM);

 }

 // 3) Считать данные от клиента (возможно, повторно)

 if (resmgr_msgread(ctp, buffer, nbytes,

start_data_offset) == -1) {

free(buffer);

return (errno);

 }

 // 4) Сделать что-нибудь с данными

 process_data(off, buffer, nbytes);

 // 5) Освободить память буфера

 free(buffer);

 // 6) Установить, сколько байт должна возвращать

 // клиентская функция «write»

 _IO_SET_WRITE_NBYTES(ctp, nbytes);

 // 7) Если данные записаны, обновить структуры

 // данных POSIX и смещение OCB

 if (nbytes) {

ocb->attr->flags |=

IOFUNC_ATTR_MTIME | IОFUNC_ATTR_DIRTY_TIME;

if (xtype == _IO_XTYPE_NONE) {

ocb->offset += nbytes;

}

 }

 // 8) Пусть библиотека сама ответит, что все в порядке

 return (EOK);

}

Как вы видите, некоторые начальные действия идентичны таковым из примера функции io_read — функция iofunc_write_verify аналогична функции iofunc_read_verify, и проверка переопределения xtype выполняется точно также.

Этап 1

Здесь мы выполняем обработку переопределения xtype, в значительной степени аналогичную примеру с io_read — за исключением того, что смещение не сохраняется в поле структуры входящего сообщения. Причина этого состоит в том, что обычной практикой для определения начального адреса поступающих от клиента данных является использование размера структуры входящего сообщения. Мы предпринимаем дополнительные усилия, чтобы удостовериться, что смещение начала данных (doffset) в коде обработки xtype является корректным.

Этап 2

Здесь мы выделяем буфер, достаточный для размещения в нем данных. Число байт, которые клиент собирается записать, представлено нам в поле nbytes объединения msg, оно заполняется автоматически Си-библиотекой клиента в коде функции write.

Отметим, что у нас недостаточно памяти для обработки запроса malloc, мы возвращаем клиенту ENOMEM, чтобы он знал, почему его запрос потерпел неудачу.

Этап 3

Здесь мы применяем вспомогательную функцию resmgr_msgread для считывания всего объема данных от клиента непосредственно в только что выделенный для этого буфер. В большинстве случаев здесь вполне сошла бы функция MsgRead, но в случаях, когда сообщение является частью составного сообщения, функция resmgr_msgread выполни для нас еще и соответствующие «магические» действия (о том, почему это надо, см. раздел «Составные сообщения»). Параметры функции resmgr_msgread довольно очевидны: мы передаем ей указатель на внутренний контекстный блок (ctp), буфер, в который мы хотим поместить данные (buffer), и число байт, которые мы хотим считать (поле nbytes объединения msg). Последний параметр — это смещение, которое мы вычислили ранее, на этапе 1. Смещение реально позволяет пропустить заголовок, помещенный туда функцией write клиентской Си-библиотеки, и сразу перейти к данным. Здесь возникает два интересных момента:

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

• мы могли бы использовать функцию resmgr_msgreadv (обратите внимание на символ «V» в названии) для считывания данных от клиента в вектор ввода/вывода, возможно, описывающий несколько разных буферов, подобно тому, как мы делали с буферами кэша при обсуждении файловых систем в главе «Обмен сообщениями».

Этап 4

Здесь вы можете делать с данными все, что вашей душе угодно — я, например, вызвал некую условную функцию process_data и передал ей буфер и его размер.

Этап 5

Критически важный этап! Упустить его из виду очень просто, но это неизбежно приведет к «утечкам памяти». Обратите также внимание, как мы позаботились об освобождении памяти в случае неудачи на этапе 3.

Этап 6

Здесь мы используем макрос _IO_SET_WRITE_NBYTES для сохранения числа записанных байт, которое затем будет передано назад клиенту в качестве значения, возвращаемого функцией write. Важно отметить, что вы должны возвратить фактическое число байт! От этого зависит судьба клиента.

Этап 7

Пришло время навести лоск для функций stat, lseek и последующих write, аналогично тому, как мы это делали в нашей io_read (и опять мы изменяем смещение в ocb только в том случае, если это не сообщение типа _IO_XTYPE_OFFSET). Однако, поскольку мы выполняем запись в устройство, мы используем при этом константу IOFUNC_ATTR_MTIME вместо константы IOFUNC_ATTR_ATIME. Флаг MTIME означает «время модификации» (modification time) — функция write определенно его изменяет.

Этап 8

Последний этап прост: мы возвращаем константу EOK, которая сообщает библиотеке администратора ресурсов, что она должна ответить клиенту. Здесь наша работа закончена. Библиотека администратора ресурсов использует в ответе данные о числе записанных байт, которые мы сохранили с помощью макроса IO_SET_WRITE_NBYTES, и разблокирует клиента. Клиентская функция write возвратит число байт, записанное нашим устройством.

Поделиться с друзьями: