int dirent_size(char *fname) {
return (ALIGN(sizeof(struct dirent) - 4 + strlen(fname)));
}
dirent_fill()И, наконец, вспомогательная подпрограмма dirent_fill() применяется для помещения передаваемых ей значений (а именно — полей inode, offset и fname) в также передаваемый ей элемент каталога. В порядке щедрости она также возвращает указатель на адрес (с учетом выравнивания), с которого должен начинаться следующий элемент каталога.
struct dirent* dirent_fill(struct dirent *dp, int inode,
int offset, char *fname) {
dp->d_ino = inode;
dp->d_offset = offset;
strcpy(dp->d_name, fname);
dp->d_namelen = strlen(dp->d_name);
dp->d_reclen =
ALIGN(sizeof(struct dirent) - 4 + dp->d_namelen);
return ((struct dirent*)((char*)dp + dp->d_reclen));
}
Написание администратора ресурсов — наиболее сложная задача из описанных в этой книге.
Администратор ресурсов — это сервер, который воспринимает ряд четко определенных сообщений. Эти сообщения можно подразделить на две категории:
Сообщения установления соединения
Относятся к операциям с именами путей, с их помощью можно устанавливать контекст для дальнейшей работы.
Сообщения ввода/вывода
Всегда следуют за сообщениями установления соединения и указывают, какую реальную работу нужно сделать для клиента (например, stat()).
Действия администратора ресурса управляются функциями пула потоков (мы обсуждали их в главе «Процессы и потоки») и функциями интерфейса диспетчеризации.
QSSL в библиотеке администратора ресурсов предоставляет ряд вспомогательных POSIX-функций, которые выполняют львиную долю работы по обслуживанию клиентских сообщений установления соединения и ввода/вывода, поступающих в администратор ресурсов.
Существует ряд структур данных, относящихся к клиентам и объявляемым администраторами ресурсов устройствам, которые необходимо принимать во внимание:
OCB (блок открытого контекста)
Выделяется при каждом «открытии» ресурса; содержат контекст для клиента (например, текущее смещение lseek()).
Атрибутная запись
Выделяется для каждого устройства; содержит информацию об устройстве (например, размер устройства, режимы доступа, и т.д.).
Запись точки монтирования
Распределяется на базисе по каждому администратору ресурса и содержит полную информацию о характеристиках администратора ресурса.
Клиент общается с администраторами ресурсов посредством обмена сообщениями, разрешив имя пути (с помощью функции open() и других вызовов) в соответствующий нужному администратору дескриптор узла, идентификатор процесса, идентификатор канала и обработчик (handle).
И наконец, вы наделяете администратор ресурса необходимой вам функциональностью, переопределяя некоторые из вызовов в таблицах функций установления соединения и функций ввода/вывода.
Приложение А
Переход с QNX4 на QNX/Neutrino
Переход с QNX4 на QNX/Neutrino
В данном приложении мы рассмотрим предыдущую операционную систему разработки QSSL, QNX4, и сравним ее с QNX/Neutrino. Данное приложение будет вам интересно главным образом в том случае, если вы уже являетесь пользователем QNX4 и хотите узнать.
• что такого замечательного в QNX/Neutrino?
• какие сложности связаны с переносом программного обеспечения в QNX/Neutrino?
а также если вы разрабатываете (или портируете) программное обеспечение для обеих операционных систем.
Давайте начнем со общих черт этих двух операционных систем:
• архитектура на основе обмена сообщениями;
• распределенный обмен сообщениями в сети;
• реальное время;
• микроядерная архитектура;
• защита памяти на уровне процессов;
• POSIX-совместимость;
• относительно простая модель «драйвера устройства»;
• встраиваемость.
Заметьте, что хоть часть вышеперечисленных базовых свойств действительно подобны в этих двух ОС, в целом QNX/Neutrino обеспечивает более расширенную поддержку. Например, поддержки POSIX в QNX/Neutrino больше, чем в QNX4, — просто потому что многие из стандартов этой серии на момент выхода QNX4 были еще только в стадии разработки. И хотя в процессе разработки QNX/Neutrino в состоянии разработки находилась гораздо меньшая их часть, постоянно появляются новые. Эта бесконечная игра в догонялки.
Теперь, когда вы выяснили, что общего между этими двумя ОС, давайте посмотрим, каковы преимущества QNX/Neutrino перед QNX4:
• большее число поддерживаемых стандартов POSIX;
• лучше встраивается;
• ядро лучше конфигурируется для других аппаратных платформ;
• поддержка многопоточности;
• более простая модель драйвера устройства;
• переносимая архитектура (в настоящее поддерживаются, кроме x86, процессоры MIPS и PPC) (а теперь уже и ARM, StrongARM и SuperH-4 — прим. ред.),
• поддерживает SMP;
• лучше документирована.
При том, что некоторые из этих усовершенствований «вне сравнения», поскольку аналогов для них в QNX4 нет, а значит, нет и проблем совместимости (например, многопоточность POSIX не поддерживалась в QNX4), некоторые из проблем потребовали внесения в ОС кардинальных изменений. Сначала я вкратце упомяну, какие классы изменений были необходимы, а затем мы подробно рассмотрим возникшие в связи с этим проблемы совместимости и предложения о том, как переносить программы в QNX/Neutrino (или заставить программу работать в обеих ОС).
Встраиваемость
В QNX/Neutrino коренным образом пересмотрена стратегия встраивания. В первоначальном варианте QNX4 встраиваемость обеспечивалась только частично. Затем появилась QNX/Neutrino, которая с самого начала разрабатывалась как встраиваемая. В качестве премии, QNX4 подверглась некоторой модернизации на основе опыта, полученного с QNX/Neutrino, и теперь является в гораздо более значительной степени встраиваемой, чем ранее. Как бы там ни было, по части встраиваемости QNX/Neutrino и QNX4 отличаются, как день и ночь. QNX4 не содержит никакой реальной поддержки таких вещей как:
• исходящие вызовы ядра (kernel callouts) (прерывание, таймер);
• настройка стартового кода;
• образная файловая система.
а вот в QNX/Neutrino все это есть. Подробное описание методик встраивания QNX/Neutrino приведено в книге «Построение встраиваемых систем», входящей в комплект документации.
Поддержка многопоточности
В QNX4 есть функция, называемая tfork(), которая позволяет вам организовать «многопоточность», создавая процесс с сегментами кода и данных, отображенными в то же адресное пространство, что и у родительского. Создание процесса и потом приведение его к «потокоподобному» виду дает иллюзию создания потока. И хотя в системе обновлений QSSL содержится библиотека поддержки потоков для QNX4, само ядро потоки непосредственно не поддерживает.
В QNX/Neutrino для организации многопоточности применяется POSIX-модель «рthread». Это означает, что там вы увидите (и уже видели в данной книге) знакомые вызовы функций типа pthread_create(), pthread_mutex_lock(), и т.п.
Обмен сообщениями
При том что воздействие потоков на обмен сообщениями может показаться минимальным, использование потоков привело к фундаментальному изменению реализации механизма обмена сообщениями (не самой концепции SEND/RECEIVE/REPLY, а именно ее реализации).
В QNX4 сообщения адресовались идентификатору процесса. Чтобы отправить сообщение, нужно было просто найти идентификатор процесса-адресата и выполнить Send(). Чтобы сервер мог принять сообщение в QNX4, он должен был вызвать Receive(). Это блокировало его до прибытия сообщения. Затем сервер отвечал с помощью функции Reply().
В QNX/Neutrino обмен сообщениями устроен так же, только при этом используются другие имена функций. Что изменилось, так это сам механизм. Теперь, прежде чем вызывать стандартные функции обмена сообщениями, клиент должен сперва создать соединение с сервером. А сервер, прежде чем он сможет выполнять стандартные функции обмена сообщениями, должен сначала создать канал.
Заметьте, что существовавшая в QNX4 функция Creceive(), которая выполняла неблокирующий вызов Receive(), в QNX/Neutrino отсутствует. Мы вообще не одобряем такие «опрашивающие» функции, особенно когда можно запустить поток; впрочем, если вы настаиваете на выполнении неблокирующего вызова MsgReceive(), посмотрите главу «Часы, таймеры и периодические уведомления», раздел «Тайм-ауты ядра». Вот соответствующий пример кода в качестве краткого пояснения:
TimerTimeout(CLOCK_REALTIME, _NTO_TIMEOUT_RECEIVE,