Роб Кёртен - Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform
Философия переноса программ
Давайте теперь взглянем на все «сверху». Здесь мы рассмотрим:
• обмен сообщениями и систему «клиент/сервер»;
• обработчики прерываний (ISR).
Анализ обмена сообщениями
В QNX4 клиент мог найти сервер двумя способами:
• используя глобальное пространство имен;
• выполнение open() в отношении администратора ввода/вывода.
«Клиент/сервер» с использованием глобального пространства именЕсли взаимоотношения «клиент/сервер, которые вы переносите, базируются на глобальном пространстве имен, тогда клиент использует:
qnx_name_locate()
а сервер регистрирует свое имя при помощи:
qnx_name_attach()
В этом случае у вас есть два выбора. Вы можете либо попробовать сохранить вариант с глобальными именами, либо модифицировать клиента и сервер так, чтобы они работали подобно стандартному администратору ресурсов.
Я рекомендую вам последний вариант, поскольку именно этот вариант характерен для QNX/Neutrino — сводить все к администраторам ресурсов, а не пытаться навесить кусок администратора ресурсов на службу глобальных имен.
Модификация будет достаточно проста. Скорее всего, клиентская сторона вызывает функцию, либо возвращающую идентификатор серверного процесса, либо создающую виртуальный канал («VC» — Virtual Circuit) от клиентского узла к удаленному узлу сервера. В обоих случаях как идентификатор процесса, так и идентификатор виртуального канала к удаленному процессу определяются при помощи qnx_name_locate(). «Магическим амулетом», связывающим клиента с сервером, здесь является специальная разновидность идентификатора процесса (мы считаем идентификатор виртуального канала идентификатором процесса, поскольку он берется из того же пула номеров и со всех точек зрения выглядит как идентификатор процесса).
Преодолеть основное различие можно было бы, возвращая вместо идентификатора процесса идентификатор соединения. Поскольку клиент в QNX4, вероятно, не анализирует идентификаторы процессов (да и зачем? Так, просто число), вы могли бы «обмануть» его, применив к «глобальному имени» функцию open(). В этом случае, однако, глобальное имя должно было бы быть точкой монтирования, зарегистрированной администратором ресурса в качестве своего «идентификатора». Вот, например, типовой пример клиента QNX4, взятый из моей серверной библиотеки CLID:
/*
* CLID_Attach(ServerName)
*
* Эта подпрограмма отвечает за установление соединения
* с сервером CLID.
*
* Возвращает PID сервера CLID или идентификатор
* виртуального канала к нему.
*/
// Сюда запишется имя - для других библиотечных вызовов
static char CLID_serverName(MAX_CLID_SERVER_NAME + 1);
// Сюда запишется идентификатор сервера
CLID static int clid_pid = -1;
int CLID_Attach(char *serverName) {
if (ServerName == NULL) {
sprintf(CLID_serverName, "/PARSE/CLID");
} else {
strcpy(CLID_serverName, serverName);
}
clid_pid = qnx_name_locate(0, CLID_serverName,
sizeof(CLID_ServerIPC), NULL);
if (clid_pid != -1) {
CLID_IPC(CLID_MsgAttach); // Послать сообщение ATTACH
return (clid_pid);
}
return (-1);
}
Вы могли бы изменить это на следующее:
/*
* CLID_Attach(serverName), версия для QNX/Neutrino
*/
int CLID_Attach(char *serverName) {
if (ServerName == NULL) {
sprintf(CLID_serverName, "/PARSE/CLID");
} else {
strcpy(CLID_serverName, serverName);
}
return (clid_pid = open(CLID_serverName, O_RDWR));
}
И клиент ничего бы не заметил.
Два замечания по реализации. В качестве зарегистрированного префикса администратора ресурса я просто оставил имя по умолчанию («/PARSE/CLID»). Вероятно, лучше было бы взять имя «/dev/clid», но насколько вы хотите следовать POSIX — это ваше личное дело. В любом случае, это незначительное изменение, и оно мало связано с тем, что здесь обсуждается.
Второе замечание касается того, что я по-прежнему назвал дескриптор файла clid_pid, хотя реально ему бы теперь следовало называться clid_fd. Это, опять же, вопрос стиля и касается только того, сколько различий вы хотите иметь между версиями кода для QNX4 и QNX/Neutrino.
В любом случае, того чтобы данная программа была переносима в обе ОС, вам придется выделить код соединения с сервером в отдельную функцию — как я это сделал выше с функцией CLID_Attach().
В какой-то момент клиент должен будет выполнить собственно операцию отправки сообщения. Здесь все становится несколько сложнее. Поскольку отношения клиент/сервер не основаны на отношениях с администраторами ввода/вывода, клиент обычно создает «нестандартные» сообщения. Снова пример из CLID-библиотеки («незащищенный» клиентский вызов здесь — CLID_AddSingleNPANXX(), я также включил функции checkAttach() и CLID_IPC() для того, чтобы продемонстрировать фактическую передачу сообщений и логику проверок):
/*
* 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().