// Установить IOV на обе части:
SETIOV(iov + 0, &whdr, sizeof(whdr));
SETIOV(iov + 1, buf, nbytes);
// Заполнить io_write_t
whdr.type = _IO_WRITE;
whdr.nbytes = nbytes;
// Отправить сообщение серверу
return (MsgSendv(coid, iov, 2, iov, 1));
}
Прежде всего, обратите внимание на то, что не применяется никакой функции iov_t
. Это структура, которая содержит два элемента — адрес и длину. Мы определили массив из двух таких структур и назвали его
Определение типа вектора iov_t
содержится в
и выглядит так:
typedef struct iovec {
void *iov_base;
size_t iov_len;
} iov_t;
Мы заполняем в этой структуре пары «адрес — длина» для заголовка операции записи (первая часть) и для данных клиента (вторая часть). Существует удобная макрокоманда,
#include
#define SETIOV(_iov, _addr, _len) \
((_iov)->iov_base = (void *)(_addr), \
(_iov)->iov_len = (_len))
Макрос iov_t
, а также адрес и данные о длине, которые подлежат записи в вектор IOV.
Также отметим, что как только мы создаем IOV для указания на заголовок, мы сможем выделить стек для заголовка без использования
В любом случае, вся важная работа выполняется функцией
#include
int MsgSendv(int coid, const iov_t *siov, int sparts,
const iov_t *riov, int rparts);
Давайте посмотрим на ее аргументы:
Идентификатор соединения, по которому мы передаем — как и при использовании функции | |
Число пересылаемых и принимаемых частей, указанных параметрами вектора iov_t ; в нашем примере мы присваиваем аргументу | |
Эти массивы значений типа iov_t указывают на пары «адрес — длина», которые мы желаем переслать. В вышеупомянутом примере мы выделяем |
Как ядро видит составное сообщение.
Ядро просто прозрачно копирует данные из каждой части вектора IOV из адресного пространства клиента в адресное пространство сервера (и обратно, при ответе на сообщение). Фактически, при этом ядро выполняет операцию фрагментации/дефрагментации сообщения (scatter/gather).
Несколько моментов, которые необходимо запомнить:
• Число фрагментов ограничено значением 231 (больше, чем вам придется использовать!); число 2 в нашем примере — типовое значение.
• Ядро просто копирует данные, указанные вектором IOV, из одного адресного пространства в другое.
•
Почему последний пункт так важен? Для того чтобы ответить, рассмотрим все подробнее. Со стороны клиента, скажем, мы выдали:
write(fd, buf, 12000);
в результате чего был создан вектор IOV из двух частей:
• заголовок (12 байт);
• данные (12000 байт);
На стороне сервера (скажем, сервера файловой системы fs-qnx4
) мы имеем блоки памяти кэша до 4Кб каждый, и мы хотели бы эффективно принять сообщение непосредственно в эти блоки. В идеале мы бы написали что-то типа:
// Настроить структуру IOV для приема: