Как вы помните из главы «Обмен сообщениями», мы упоминали еще несколько функций обмена сообщениями, а именно — функции 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()).
/*
* io_write1.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
void process_data(int offset, void *buffer, int nbytes) {
// Сделать что-нибудь с данными
}
int io_write(resmgr_context_t *ctp, io_write_t *msg,
iofunc_ocb_t *ocb) {
int sts;
int nbytes;
int off;
int start_data_offset;
int xtype;
char *buffer;
struct _xtype_offset *xoffset;
// Проверить, открыто ли устройство на запись
if ((sts =
iofunc_write_verify(ctp, msg, ocb, NULL)) != EOK) {
return (sts);
}
// 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);