Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
Шрифт:
• Мы были должны скопировать данные дважды: в первый раз — при использовании функции memcpy, и затем еще раз, снова — уже при осуществлении передачи сообщения.
• Мы должны были предусмотреть указатель на тип
Поскольку ядро намерено копировать данные в любом случае, было бы хорошо, если бы мы смогли сообщить ему о том, что одна часть данных (заголовок) фиксирована по некоторому адресу, а другая часть (собственно данные) фиксирована где- нибудь еще, без необходимости самим вручную собирать буферы из частей и копировать данные.
На наше счастье, в QNX/Neutrino реализован механизм, который позволяет нам сделать именно так! Механизм этот называется IOV (i/o vector),
Давайте для начала рассмотрим некоторую программу, а затем обсудим, что происходит с применением такого вектора.
Прежде всего, обратите внимание на то, что не применяется никакой функции malloc и никакой функции memcpy. Затем обратим внимание на тип применяемого вектора IOV —
Определение типа вектора
Мы заполняем в этой структуре пары «адрес — длина» для заголовка операции записи (первая часть) и для данных клиента (вторая часть). Существует удобная макрокоманда, SETIOV, которая выполняет за нас необходимые присвоения. Она формально определена следующим образом:
Макрос SETIOV принимает вектор
Также отметим, что как только мы создаем IOV для указания на заголовок, мы сможем выделить стек для заголовка без использования malloc. Это может быть и хорошо, и плохо — это хорошо, когда заголовок невелик, потому что вы хотите исключить головные боли, связанные с динамическим распределением памяти, но это может быть плохо, когда заголовок очень велик, потому что тогда он займет слишком много стекового пространства. Впрочем, заголовки обычно невелики.
В любом случае, вся важная работа выполняется функцией MsgSendv, которая принимает почти те же самые аргументы, что и функция MsgSend, которую мы использовали в предыдущем примере:
Давайте посмотрим на ее аргументы:
coid | Идентификатор соединения, по которому мы передаем — как и при использовании функции MsgSend. |
sparts и rparts | Число пересылаемых и принимаемых частей, указанных параметрами вектора iov_t ;
в нашем примере мы присваиваем аргументу sparts значение 2, указывая этим, что пересылаем сообщение из двух частей, а аргументу rparts — значение 1, указывая этим, что мы принимаем ответ из одной части. |
siov и riov | Эти массивы значений типа iov_t указывают на пары «адрес — длина», которые мы желаем переслать. В вышеупомянутом примере мы выделяем siov из двух частей, указывая ими на заголовок и данные клиента, и riov из одной части, указывая им только на заголовок. |
Как ядро видит составное сообщение.
Ядро просто прозрачно копирует данные из каждой части вектора IOV из адресного пространства клиента в адресное пространство сервера (и обратно, при ответе на сообщение). Фактически, при этом ядро выполняет операцию фрагментации/дефрагментации сообщения (scatter/gather).
Несколько моментов, которые необходимо запомнить:
• Число фрагментов ограничено значением 231 (больше, чем вам придется использовать!); число 2 в нашем примере — типовое значение.
• Ядро просто копирует данные, указанные вектором IOV, из одного адресного пространства в другое.
• Вектор-источник и вектор-приемник не должны совпадать.
Почему последний пункт так важен? Для того чтобы ответить, рассмотрим все подробнее. Со стороны клиента, скажем, мы выдали:
в результате чего был создан вектор IOV из двух частей:
• заголовок (12 байт);
• данные (12000 байт);
На стороне сервера (скажем, сервера файловой системы
Эта программа делает в значительной степени то, что вы и предполагаете: она задает вектор IOV из 4 частей, первая из которых указывает на заголовок, а следующие три части — на блоки кэш-памяти с номерами 37, 16 и 22. (Предположим, что именно эти блоки случайно оказались доступными в данный момент.) Ниже это иллюстрируется графически.
Распределение непрерывных данных по отдельным буферам.
Затем осуществляется вызов функции MsgReceivev, и ей указывается, что мы намерены принять сообщение по указанному каналу (параметр chid), и что вектор IOV для этой операции состоит из 4 частей.
(Кроме возможности работать с векторами IOV, функция MsgReceivev действует аналогично функции MsgReceive.)
Опа! Мы сделали ту же самую ошибку, которую уже делали к раньше, когда знакомились с функцией MsgReceive. Как мы узнаем, сообщение какого типа мы собираемся принять и сколько в нем данных, пока не примем все сообщение целиком?
Мы сможем решить эту проблему тем же способом, что и прежде: