Выбрать главу

/*

 * CLID_AddSingleNPANXX(npa, nxx)

*/

int CLID_AddSingleNPANXX(int npa, int nxx) {

 checkAttach();

 CLID_IPCData.npa = npa;

 CLID_IPCData.nxx = nxx;

 CLID_IPC(CLID_MsgAddSingleNPANXX);

 return (CLID_IPCData.returnValue);

}

/*

 * CLID_IPC(номер_сообщения_IPC)

 *

 * Эта подпрограмма вызывает сервер с глобальным буфером

 * CLID_IPCData и заносит в него номер сообщения,

 * переданный ей в качестве аргумента.

 *

 * Если сервера нет, эта подпрограмма установит

 * поле returnValue в CLID_NoServer. Остальные

 * поля остаются как есть.

*/

void CLID_IPC(int IPCMessage) {

 if (clid_pid == -1) {

  CLID_IPCData.returnValue = CLID_NoServer;

  return;

 }

 CLID_IPCData.serverFunction = IPCMessage;

 CLID_IPCData.type = 0x8001;

 CLID_IPCData.subtype = 0;

 if (Send(clid_pid, &CLID_IPCData, &CLID_IPCData,

  sizeof(CLID_IPCData), sizeof(CLID_IPCData))) {

  CLID_IPCData.returnValue = CLID_IPCError;

  return;

 }

}

void checkAttach() {

 if (clid_pid == -1) {

  CLID_Attach(NULL);

 }

}

Как вы видите, функция checkAttach() применяется для проверки существования соединения с сервером CLID. Если бы соединения не было, это было бы подобно запросу read() по несуществующему дескриптору файла. В моем варианте программы функция checkAttach() создает соединение автоматически. Это как если бы функция read() определила, что дескриптор файла некорректен, и сама создала бы корректный. Еще один вопрос стиля.

Обмен специализированными сообщениями происходит в функции CLID_IPC(). Она берет значение глобальной переменной CLID_IPCData и пробует переслать его серверу, используя функцию QNX4 Send().

Специализированные сообщения могут быть обработаны о из двух способов. Можно:

1. транслировать их в стандартные вызовы функций POSIX основанные на файловых дескрипторах;

2. инкапсулировать их в сообщение типа devctl(), либо в специализированное сообщение, используя тип _IO_MSG.

В обоих случаях вы перестраиваете клиента на обмен сообщениями стандартными для администраторов ресурсов средствами. Как? У вас нет файлового дескриптора? Есть только идентификатор соединения? Или наоборот? Ну, это как раз не проблема. В QNX/Neutrino дескриптор файла в действительности является идентификатором соединения!

Трансляция сообщений в стандартные вызовы POSIX на основе файловых дескрипторов

В случае CLID-сервера это не вариант. Не существует стандартного POSIX-вызова на основе файлового дескриптора, который мог бы «добавить к администратору ресурса CLID пару NPA/NXX». Однако, существует стандартный механизм devctl(), так что если ваши отношения клиент/сервер требуют такой формы, смотрите ниже.

Прежде чем броситься реализовывать этот подход (трансляцию в стандартные сообщения на основе файловых дескрипторов), давайте остановимся и подумаем, где это может оказаться полезным. В аудиодрайвере QNX4 вы могли бы использовать нестандартные сообщения для передачи аудиоданных администратору и от него. При ближайшем рассмотрении здесь, для задачи блочной передачи данных, вероятно, наиболее бы подошли функции read() и write(). Установку частоты оцифровки, с другой стороны, можно было бы гораздо удачнее реализовать с применением функции devctl().

Хорошо, но ведь не каждое взаимодействие клиент/сервер сводится к блочной передаче данных (тот же сервер CLID — тому пример).

Трансляция сообщений в вызовы devctl() или _IO_MSG

Итак, возник вопрос — как выполнять операции управления? Самый простой способ состоит в применении POSIX-вызова devctl(). Наш пример из библиотеки CLID примет вид:

/*

 * CLID_AddSingleNPANXX(npa, nxx)

*/

int CLID_AddSingleNPANXX(int npa, int nxx) {

 struct clid_addnpanxx_t msg;

 checkAttach(); // Оставить или нет — дело вкуса

 msg.npa = npa;

 msg.nxx = nxx;

 return

  (devctl(clid_pid, DCMD_CLID_ADD_NPANXX,

  &msg, sizeof(msg), NULL));

}

Как видите, операция относительно безболезненная. (Для тех, кто не любит devctl() за то, что приходится отправлять в обе стороны блоки данных одного и того же размера, см. ниже обсуждение сообщений _IO_MSG.). Опять же, если вы пишете программу, которая должна работать в обеих операционных системах, вам следует выделить функцию обмена сообщениями в отдельный библиотечный модуль и предоставить несколько вариантов реализации, в зависимости от применяемой операционной системы.

Реально мы убили двух зайцев:

1. отказались от глобальной переменной и стали собирать сообщения на основе стековой переменной — это делает нашу программу безопасной в многопоточной среде (thread- safe);

2. передали структуру данных нужного размера вместо структуры данных максимального размера, как мы это делали в предыдущем примере с QNX4.

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

Второй убитый заяц заключался в том, что мы передали «структуру данных нужного размера». На самом деле, мы тут немножко приврали. Обратите внимание на то, что функция devctl() имеет только один параметр размера (четвертый, который мы установили в sizeof(msg)). Как на самом деле происходит пересылка данных? Второй параметр функции devctl() содержит команду для устройства (поэтому и «DCMD»). Двумя старшими битами команды кодируется направление, которое может быть одним из четырех:

1. «00» — передачи данных нет;

2. «01» — передача от драйвера клиенту;

3. «10» — передача от клиента драйверу;

4. «11» — двунаправленная передача.

Если вы не передаете данные (то есть достаточно просто команды) или передаете их в одном направлении, то применение функции devctl() — прекрасный выбор. Интересен тот вариант, когда вы передаете данные в обоих направлениях. Интересен он тем, что, поскольку у функции devctl() только один параметр размера, обе пересылки данных (как драйверу, так и от драйвера) передадут весь буфер данных целиком! Это хорошо в том частном случае, когда размеры буферов «ввода» и «вывода» одинаковы, но представьте себе, что буфер принимаемых драйвером данных имеет размер в несколько байт, а буфер передаваемых данных гораздо больше. Поскольку у нас есть только один параметр размера, мы вынуждены будем каждый раз передавать драйверу полный буфер данных, хотя требовалось передать всего несколько байт!