Операционная система QNX 4 Архитектура системы СОДЕРЖАНИЕ Об этой книге 7 1. Система QNX 8 1.1. Что такое QNX 8 1.2. Архитектура ядра системы QNX 8 1.2.1. Ядро системы QNX 8 1.2.2. Системные процессы 9 1.2.2.1. Системные процессы и процессы пользователя 9 1.2.2.2. Драйверы устойств 9 1.3. Связь между процессами 9 1.3.1. Операционная система с передачей сообщений 9 1.4. QNX как сеть 10 1.4.1. Однокомпьютерная модель 10 1.4.2. Гибкая сетевая обработка 10 2. Микроядро 12 2.1. Введение 12 2.2. Связь между процессами 12 2.3. Связь между процессами посредством сообщений 12 2.3.1. Передача сообщений 13 2.3.2. Синхронизация процессов 13 2.3.3. Состояния блокировок 14 2.3.4. Использование функций Send(), Receive() и Reply() 12 2.3.4.1. Использование функции Send() 14 2.3.4.2. Использование функции Receive() 15 2.3.4.3. Использование функции Reply() 15 2.3.5. Reply-управляемая передача сообщений 15 2.3.6. Дополнительная информация 15 2.3.7. Дополнительные возможности передачи сообщений 16 2.3.7.1. Условный прием сообщений 16 2.3.7.2. Чтение или запись части сообщения 17 2.3.7.3. Передача составных сообщений 17 2.3.8. Зарезервированные коды сообщений 17 2.4. Связь между процессами посредством proxy 18 2.5. Связь между процессами посредством сигналов 18 2.5.1. Генерация сигналов 18 2.5.2. Прием сигналов 19 2.5.3. Перечень сигналов 19 2.5.4. Определение способа обработки сигнала 20 2.5.5. Обработка сигналов 20 2.5.6. Рекомендуемые функции для обработчиков сигналов 21 2.5.7. Блокировка сигналов 21 2.5.8. Сигналы и сообщения 21 2.6. Связь между процессами в сети 22 2.6.1. Виртуальные каналы 22 2.6.2. Виртуальные процессы 22 2.6.3. Отключение виртуальных каналов 23 2.7. Планирование процессов 23 2.7.1. Когда выполняется планирование 23 2.7.2. Приоритеты процессов 24 2.7.3. Методы планирования 24 2.7.3.1. Планирование по принципу простой очереди 25 2.7.3.2. Круговой метод планирования 25 2.7.3.3. Адаптивное планирование 26 2.7.3.4. Приоритет, управляемый клиентом 26 2.8. О работе в реальном времени 27 2.8.1. Задержка прерывания 27 2.8.2. Задержка планирования 27 2.8.3. Вложенные прерывания 28 3. Администратор процессов 30 3.1. Введение 30 3.1.1. Функции Администратора процессов 30 3.1.2. Примитивы создания процесса 30 3.1.2.1. Функция fork() 30 3.1.2.2. Функция exec() 30 3.1.2.3. Функция spawn() 30 3.1.3. Что наследует процесс 30 3.2. Жизненный цикл процесса 31 3.2.1. Создание 31 3.2.2. Загрузка 31 3.2.3. Выполнение 31 3.2.4. Завершение 31 3.3. Состояния процессов 32 3.3.1 Определение состояний процессов 33 3.4. Символические имена процессов 33 3.5. Таймеры 34 3.5.1. Управление временем 34 3.5.2. Простые средства таймирования 34 3.5.3. Более сложные средства таймирования 34 3.5.3.1. Создание таймеров 34 3.5.3.2. Установка таймеров 35 3.5.3.3. Удаление таймеров 35 3.5.3.4. Установка периода таймера 35 3.5.3.5. Считывание таймера 35 3.6. Обработчики прерываний 35 3.6.1. Обработчики прерываний от таймера 36 4. Пространство имен системы ввода/вывода 37 4.1. Введение 37 4.1.1. Префиксы и области полномочий 37 4.2. Составные имена 37 4.2.1. Префиксы Администратора ввода/вывода 37 4.2.2. Сетевой корень 38 4.2.3. Сетевой корень по умолчанию 38 4.2.3.1. Передача составных имен между процессами 39 4.2.4. Альтернативные префиксы 39 4.2.4.1. Создание специальных имен устройств 40 4.2.5. Относительные составные имена 40 4.2.6. Текущий рабочий каталог 40 4.2.6.1. О команде cd 40 4.3. Пространство имен описателей файлов 41 4.3.1. Управляющие блоки открытия 41 5. Администратор файловой системы 44 5.1. Введение 44 5.2. Что такое файл 44 5.2.1. Метки даты и времени 44 5.2.2. Доступ к файлу 44 5.3. Регулярные файлы и каталоги 45 5.3.1. Регулярные файлы 45 5.3.2. Каталоги 45 5.3.2.1. Операции с каталогами 45 5.3.2.2. Чтение элементов каталога 46 5.3.3. Экстенты 46 5.3.3.1. Где хранятся экстенты 46 5.3.3.2. Увеличение размера файла 46 5.4. Связи и индексные дескрипторы 47 5.4.1. Удаление связей 47 5.4.2. Связи каталога 48 5.5. Символические связи 48 5.6. Программные каналы и FIFO-файлы 49 5.6.1. Программные каналы 49 5.6.2. FIFO-файлы 50 5.7. Производительность Администратора файловой системы 50 5.7.1. Элеваторный доступ 50 5.7.2. Кэш-буфер 50 5.7.3. Многопоточная обработка 51 5.7.4. Управляемый процессами приоритет 51 5.7.5. Временные файлы 51 5.7.6. Электронные диски 51 5.8. Живучесть файловой системы 51 5.8.1. Восстановление файловой системы 52 5.9. Исходные тома 52 5.9.1. Диски и дисковые подсистемы 52 5.9.2. Разделы операционной системы 52 5.9.3. Определение блок-ориентированных специальных файлов 53 5.9.4. Монтирование файловой системы 54 5.9.5. Демонтирование файловой системы 54 5.10. Ключевые компоненты раздела QNX 54 5.10.1. Блок загрузчика 55 5.10.2. Корневой блок 55 5.10.3. Битовая карта 56 5.10.4. Корневой каталог 56 5.11. Администратор файловой системы DOS 56 6. Администратор устройств 57 6.1. Введение 57 6.2. Работа с устройствами 57 6.3. Режим редактируемого ввода 57 6.4. Режим нередактируемого ввода 58 6.5. Драйверы устройств 60 6.5.1. Управление устройствами 61 6.6. Консоль QNX 61 6.6.1. Специфические для консоли функции 61 6.7. Устройства с последовательным интерфейсом 62 6.8. Устройства с параллельным интерфейсом 62 6.9. Производительность подсистемы периферийных устройств 63 7. Сетевой администратор 64 7.1. Введение 64 7.2. Область действия Сетевого администратора 64 7.2.1. Независимый модуль 64 7.3. Интерфейс микроядро/Сетевой администратор 64 7.4. Сетевые драйверы 66 7.5. Идентификаторы узлов и сетей 66 7.5.1. Физические идентификаторы узлов 67 7.5.2. Логические идентификаторы узлов 67 7.5.3. Идентификаторы логических сетей 67 7.6. Выбор сети 68 7.6.1. Балансировка нагрузки 68 7.6.2. Отказоустойчивость 68 ОБ ЭТОЙ КНИГЕ Книга "Операционная система QNX 4. Архитектура системы" сопровождает операционную систему QNX и предназначена как для разработчиков приложений, так и для конечных пользователей. В книге подробно рассказывается о стуктуре и функциях системы QNX. В ней содержится описание микроядра, системных администраторов, а также уникального механизма связи между процессами, основанного на передаче сообщений. Прежде чем использовать QNX, рекомендуем сначала ознакомиться с этой книгой. Информация по инсталляции и использованию QNX содержится в Руководстве пользователя. Данная книга содержит следующие разделы: Система QNX; Микроядро; Администратор процессов; Пространство имен системы ввода/вывода; Администратор файловой системы; Администратор устройств; Сетевой администратор. Условные обозначения Для обозначения конкретных технических терминов в тексте книги использованы определенные типографские шрифты. В основном они соответствуют шрифтам публикаций IEEE POSIX. Примеры условных обозначений сведены в следующую таблицу. ------------------------------------------------------------- Термины Пример обозначения ------------------------------------------------------------- Имена команд ls Типы данных long Переменные среды PATH Номера ошибок [EINTR] Имена файлов /dev/tty Функции system() Аргументы функций arg1 Глобальные переменные errno Заголовочные файлы Опции -c Аргументы опций -w width Операнды file_name Параметры Символические константы {_POSIX_VDISABLE} ------------------------------------------------------------- 1. СИСТЕМА QNX 1.1. Что такое QNX Основным назначением любой операционной системы (ОС) является управление ресурсами компьютера. Все процессы в системе: планирование выполнения прикладных программ, запись файлов на диск, пересылка данных по сети и т.д., - должны выполняться как можно более единообразно и бесконфликтно. Некоторые прикладные системы могут предъявлять повышенные требования к управлению ресурсами и планированию процессов. Например, работа приложений реального времени зависит от того, как операционная система управляет большим количеством событий, возникающих за конечные интервалы времени. Чем больше функций берет на себя ОС, тем более свободно "чувствуют" себя эти приложения при возникновении конфликтных ситуаций. Для приложений, работающих в режиме реального времени, QNX является идеальной операционной системой. Она удовлетворяет всем основным требованиям, предъявляемым к системам реального времени: в ней реализован многозадачный режим, приоритетно-управляемое планирование и быстрое переключение контекста. Кроме того, система QNX обладает большой гибкостью. Разработчики могут легко адаптировать ее под требования своих приложений. Настройка системы QNX может быть выполнена от минимальной (ядро и несколько небольших модулей) до полной сетевой конфигурации (обслуживание сотен пользователей), позволяя использовать в каждом конкретном случае только те ресурсы, которые необходимы. Уникальная эффективность, модульность и простота системы QNX определяется: - архитектурой ядра; - взаимодействием между процессами посредством сообщений. 1.2. Архитектура ядра системы QNX Система QNX состоит из небольшого ядра (микроядра) и набора взаимодействующих процессов. Как показано на рис. 1, система не имеет иерархической структуры, ее организация скорее напоминает "спортивную команду", в которой игроки (процессы), имеющие равную значимость, взаимодействуют друг с другом и со своим "ведущим игроком" (ядром). +-----------------------+ +------------------------+ | Администратор | | Администратор | | процессов +--+ +--| файловой системы | +-----------------------+ | | +------------------------+ +------------+ | Микроядро | +------------+ +-------------------------+ | | +------------------------+ | Администратор | | | | Сетевой | | устройств +-+ +--| администратор | +-------------------------+ +------------------------+ Микроядро системы QNX координирует работу системных администраторов. Рис. 1 1.2.1. Ядро системы QNX Ядро является "сердцем" любой операционной системы. В некоторых системах на ядро возложено такое количество функций, что, по сути дела, оно само является полной операционной системой. В системе QNX ядро является действительно ядром. Прежде всего, как и подобает ядру операционной системы реального времени, оно имеет небольшой размер_-_менее 8 Кбайт. На ядро системы QNX возложено выполнение только двух основных функций: -_передача сообщений (ядро реализует передачу всех сообщений между всеми процессами во всей системе); -_планирование (планировщик является частью ядра и подключается каждый раз, когда процесс меняет свое состояние в результате появления сообщения или прерывания). В отличие от процессов само ядро никогда не планируется к выполнению. Управление передается ядру только в результате прямого вызова ядра либо из процесса, либо по аппаратному прерыванию. 1.2.2. Системные процессы Все функции, выполняемые операционной системой QNX, за исключением функций ядра, реализуются стандартными процессами. В типичной конфигурации системы QNX имеются следующие системные процессы: - Администратор процессов (Proc); - Администратор файловой системы (Fsys); - Администратор устройств (Dev); - Сетевой администратор (Net). 1.2.2.1. Системные процессы и процессы пользователя Системные процессы практически ничем не отличаются от любого процесса пользователя: у них нет специального или скрытого интерфейса, недоступного процессу пользователя. Именно такая архитектура обеспечивает системе QNX неограниченную расширяемость. Поскольку большинство функций QNX выполняется стандартными системными процессами, то расширить операционную систему совсем не сложно: достаточно написать и включить в систему программу, реализующую новую функцию ОС. Действительно, грань между операционной системой и прикладными программами весьма условна. Единственным принципиальным отличием системных процессов от прикладных является то, что системные процессы управляют ресурсами системы, предоставляя их прикладным процессам. Предположим, что вы написали сервер базы данных. Как следует классифицировать эту программу? Сервер базы данных должен выполнять функции, аналогичные функциям Администратора файловой системы, который получает запросы (сообщения) на открытие файлов и чтение или запись данных. Несмотря на то, что запросы к серверу базы данных могут быть более сложными, и в том и в другом случае формируется набор примитивов (посредством сообщений), в результате чего обеспечивается доступ к системному ресурсу. В обоих случаях речь идет о процессах, которые могут быть написаны конечным пользователем и выполняться по необходимости. Таким образом, сервер базы данных можно рассматривать как системный процесс в одном случае и как прикладной в другом. Фактически нет никакой разницы. Важно отметить, что в системе QNX подобные процессы включаются без каких бы то ни было модификаций других компонентов операционной системы. 1.2.2.2. Драйверы устройств Драйверы устройств - это процессы, которые избавляют операционную систему от необходимости иметь дело со всеми особенностями работы аппаратного обеспечения. Поскольку драйверы выполняются как стандартные процессы, то добавление нового драйвера в систему QNX не влияет на работу других компонентов операционной системы. Единственное изменение, которое происходит в среде QNX - это запуск нового драйвера. Драйвер может быть оформлен либо как дополнение к системному процессу, либо, для сохранения его "индивидуальности", в качестве стандартного процесса. 1.3. Связь между процессами В типичной многозадачной среде, при одновременном выполнении нескольких процессов реального времени, операционная система должна обеспечивать возможность взаимодействия процессов друг с другом. Связь между процессами (interprocess communication - IPC) является ключом к разработке приложений, представляющих собой набор взаимосвязанных процессов, в котором каждый процесс выполняетет одну строго определенную функцию. В QNX реализован простой, но мощный набор IPC-возможностей, благодаря которому значительно упрощается разработка приложений, представляющих набор взаимодействующих процессов. 1.3.1. Операционная система с передачей сообщений QNX стала первой коммерческой операционной системой данного класса, в которой IPC основан на принципе передачи сообщений. Именно благодаря глобальному использованию передачи сообщений во всей системе, ОС QNX обладает присущей ей мощностью, простотой и элегантностью. В системе QNX под сообщением понимается пакет байтов, передаваемый от одного процесса к другому. QNX не предъявляет никаких требований к содержимому сообщения: данные в сообщении имеют значение только для его отправителя и получателя. Посредством сообщений происходит не только передача данных между процессами, но также и синхронизация выполнения нескольких процессов. При передаче, получении и выдаче ответа на сообщения процессы изменяют свое состояние, что определяет время и продолжительность их выполнения. Располагая информацией о состоянии и приоритетах процессов, ядро может максимально эффективно планировать их выполнение, используя все доступные ресурсы центрального процессора. Этот метод передачи сообщений используется глобально во всей системе. Для приложений реального времени требуется зависимая форма IPC, т.к. процессы, входящие в состав подобных приложений, сильно взаимосвязаны. Механизм передачи сообщений, реализованный в системе QNX, позволяет обеспечить высокую надежность работы приложений. 1.4. QNX как сеть В простейшем случае локальная вычислительная сеть реализует механизм разделения файлов и внешних устройств между несколькими взаимосвязанными компьютерами. В системе QNX эта концепция получила дальнейшее развитие, в результате чего вся сеть стала представлять собой единый однородный набор ресурсов. Любой процесс на любой машине сети может использовать любой ресурс любой другой машины. C точки зрения приложения нет никакой разницы между своим или удаленным ресурсом: приложению не требуется иметь никаких специальных средств для обеспечения доступа к удаленному ресурсу. Фактически программа указывает только специальный код, для того, чтобы система определила принадлежит ли ресурс, например, файл или устройство, своему компьютеру или другому узлу сети. Пользователи имеют доступ ко всем файлам сети, могут использовать любое внешнее устройство и запускать приложения на любой машине сети (при условии, что они имееют на это соответствующее полномочие). Соответственно, все процессы могут взаимодействовать между собой по всей сети. Таким образом, механизм передачи сообщений, средствами которого реализован IPC в системе QNX, обеспечивает гибкую и прозразную сетевую обработку. 1.4.1. Однокомпьютерная модель QNX изначально разрабатывалась как сетевая операционная система. В некоторых аспектах сеть QNX скорее похожа на универсальную вычислительную машину (mainframe), чем на набор микрокомпьютеров. В частности, благодаря большому набору ресурсов, которые могут быть доступны любому приложению. Но в отличие от универсальной машины QNX обеспечивает высокореактивную среду, поскольку любой узел сети может задействовать требуемые ему вычислительные мощности. Например, программы, управляющие работой устройств ввода/вывода, работающие в реальном времени, могут потребовать больше ресурсов, чем другие, менее критичные ко времени, приложения (текстовый процессор). Сеть QNX достаточно реактивна для того, чтобы поддерживать оба типа приложений одновременно: QNX позволяет сконцентрировать вычислительную мощность там, где и когда это необходимо без ущерба для параллельной обработки данных. 1.4.2. Гибкая сетевая обработка Сети QNX могут быть объединены на базе различного аппаратного обеспечения и различных протоколов промышленного стандарта. Поскольку эти сети являются совершенно прозрачными для прикладных программ, то новая сетевая архитектура может быть внедрена в любое время без внесения изменений в операционную систему. ВНИМАНИЕ. Список сетевого оборудования, которое поддерживает QNX, постоянно растет. Более подробную информацию вы можете получить в документации по тому сетевому оборудованию, которое вы собираетесь использовать. Каждому узлу сети QNX присвоен номер, являющийся его идентификатором. Этот номер служит единственным признаком, позволяющим определить, работает ли QNX как сеть или как однопроцессорная операционная система. Такая высокая степень прозрачности является еще одним примером исключительной мощности архитектуры ОС QNX, основанной на передаче сообщений. Во многих операционных системах такие функции, как сетевая обработка, связь между процессами (IPC) или даже передача сообщений, выполняются не ядром, а надстройкой над ОС. В результате получается совершенно неэффективный интерфейс "двойного стандарта", в котором отдельно организована связь между процессами и совершенно иначе - доступ к закрытому монолитному ядру. В основе системы QNX лежит принцип: эффективная связь - ключ к эффективной работе. Таким образом, принцип передачи сообщений является определяющим для архитектуры системы QNX. Благодаря ему, обеспечивается эффективная передача всех транзакций между процессами системы, как по внутренней шине компьютера, так и по сети. Давайте теперь познакомимся ближе со структурой и функциями системы QNX. 2. МИКРОЯДРО 2.1. Введение Ядро операционной системы QNX, представленное на рис. 2, выполняет следующие функции: -_связь между процессами (IPC). Ядро обеспечивает три формы IPC (сообщения, proxy (прокси) и сигналы); -_сетевое взаимодействие нижнего уровня. Ядро передает все сообщения, предназначенные процессам на другом узле; -_планирование процессов. Планировщик ядра определяет, какой процесс будет выполняться следующим; -_первичную обработку прерываний. Все прерывания и сбои аппаратного обеспечения сначала обрабатываются в ядре, а затем передаются соответствующему драйверу или системному администратору. процессы +-------+ +-------+ +-------+ +-------+ +>| | | | | | | | | +-------+ +-------+ +-------+ +-------+ | | | | | | +--------+ | +--------+ | | | | | +-----------------+ | +----------------------------------+ +---------------+ | | | Сетевой | | Администратор | | | | интерфейс +------| сети | | | +--------------| +---------------+ | | IPC | |  | | | Планировщик | | | | | | | | | | | | | +-------------------| |  | | Планировщик | | --------------- +------| аппаратных | | | прерываний | | +----------------------------------+    | | | прерывания Рис. 2. Микроядро 2.2. Связь между процессами Ядро QNX поддерживает три типа связи между процессами: сообщениями, proxy и сигналами. Основной формой IPC в системе QNX является обмен сообщениями. Сообщения обеспечивают синхронную связь между взаимодействующими процессами, при которой процесс, посылающий сообщение, требует подтверждения о его приеме и, возможно, ответа на сообщение. Связь посредством proxy представляет собой особый вид передачи сообщений. Этот вид связи специально предназначен для оповещения о событиях, при которых процесс-отправитель не нуждается во взаимодействии с процессом-получателем. Связь сигналами - это традиционная форма IPC. Сигналы используются для обеспечения асинхронной связи между процессами. 2.3. Связь между процессами посредством сообщений В системе QNX под сообщением понимается пакет байтов, который синхронно передается от одного процесса к другому. Для самой системы содержимое сообщения не имеет никакого значения. Данные в сообщении имеют смысл только для отправителя и получателя. 2.3.1. Передача сообщений Для прямой связи друг с другом взаимодействующие процессы используют следующие функции языка Си: Send() - для посылки сообщений; Receive() - для приема сообщений; Reply() - для ответа процессу, пославшему сообщение. Эти функции могут использоваться локально или по всей сети. Обратите внимание на то, что для прямой связи процессов друг с другом необязательно использование функций Send(), Receive() и Reply(). Система библиотечных функций QNX надстроена над системой обмена сообщениями, поэтому процессы могут использовать передачу сообщений косвенно при использовании стандартных сервисных средств, например, программных каналов (pipe). +-------------+ +---------------+ | | процесс А | | процесс В | | +-------------+ +---------------+ Время |  | | +---+ передача данных | | Send() +---+ функцией Send()   - - - - - - - - - - - - - >+---+Receive () передача данных +---+ функцией Reply()  +---+<- - - - - - - - - - - - - - +---+ +---+ +---+Reply()   Процесс А посылает сообщение процессу В, который принимает его, обрабатывает и передает ответ. Рис. 3 Приведенный выше рис. 3 иллюстрирует простой пример использования функций Send(), Receive() и Reply() при взаимодействии двух процессов - А и В: 1)_Процесс А посылает сообщение процессу В, выдав ядру запрос Send(). С этого момента процесс А становится SEND-блокированным до тех пор, пока процесс В не выдаст Receive(), подтверждая получение сообщения; 2)_Процесс В выдает Receive() процессу А, ожидающему сообщения. Процесс А изменяет свое состояние на REPLY-блокированное. Поскольку от процесса В ожидается сообщение, он не блокируется. Обратите внимание на то, что, если бы процесс В выдал Receive() до отправления ему сообщения, он оставался бы RECEIVE-блокированным до момента приема сообщения. В этом случае процесс А (отправитель) перешел бы сразу в REPLY-блокированное состояние после отправления сообщения процессу В; 3)_Процесс В выполняет необходимую обработку, определяемую полученным от процесса А сообщением, и выдает Reply(). Процесс А получает ответное сообщение и разблокировывается. Процесс В также разблокировывается. Какой из процессов начнет выполняться первым, зависит от их относительных приоритетов. 2.3.2. Синхронизация процессов Передача сообщений служит не только для обмена данными между процессами, но, кроме того, является средством синхронизации выполнения нескольких взаимодействующих процессов. Давайте обратимся снова к приведенному выше рис. 3. После того, как процесс А выдаст запрос Send(), он не сможет выполняться до тех пор, пока не получит ответа на переданное им сообщение. Это служит гарантией того, что обработка данных, выполняемая процессом В для процесса А завершится до того, как процесс А сможет продолжить свою работу. В свою очередь процесс В после выдачи запроса Receive() может продолжать свою работу до поступления другого сообщения. ВНИМАНИЕ. Более подробно механизм планирования работы процессов рассмотрен в подразделе 2.7 "Планирование процессов". 2.3.3. Состояния блокировок Когда выполнение процесса запрещено до момента завершения некоторой части протокола обмена сообщениями, говорят, что процесс "блокирован". Состояния блокировок процессов приведены в следующей таблице. ---------------------------------------------------------------- Если процесс выдал Процесс является ---------------------------------------------------------------- Запрос Send(), и отправленное им сообщение еще не получено SEND-блокированным процессом-получателем Запрос Send(), и отправленное им сообщение получено REPLY-блокированным процессом-получателем, но ответ еще не выдан Запрос Receive(), но сам еще не получил сообщение RECEIVE-блокированным ----------------------------------------------------------------- Состояния процесса в типичной транзакции Send-Receive-Reply представлены на рис. 4. Reply() +--------+ +--------------+ | | Send() | Процесс | Send() |  +-->| SEND- | +----------------+ +---------------+ | | блокирован | | Процесс +---->| Процесс в +---+ +--------------+ | RECEIVE- | | состоянии | | Receive() | блокирован |<----| READY |<--+  +----------------+ +---------------+ | +--------------+ Receive() | | Процесс | +---| REPLY- | Reply() | блокирован | +--------------+ Send() - функция выдается данным процессом Send() - функция выдается другим процессом Рис. 4 ВНИМАНИЕ. Другие возможные состояния процессов рассмотрены в подразделе 3.3. 2.3.4. Использование функций Send(), Receive() и Reply() Давайте теперь рассмотрим функции Send(), Receive() и Reply() более подробно. По-прежнему будем пользоваться нашим примером взаимодействия процессов А и В. 2.3.4.1. Использование функции Send() Предположим, что процесс А выдает запрос на передачу сообщения процессу В. Запрос оформляется вызовом функции Send() Send (pid, smsg, rmsg, smsg_bn, rmsg_len); Функция Send() имеет следующие аргументы: pid -_идентификатор процесса-получателя сообщения (т.е. процесса В); pid - это идентификатор, посредством которого процесс опознается операционной системой и другими процессами; smsg -_буфер сообщения (т.е. посылаемого сообщения); rmsg -_буфер ответа (в который помещается ответ процесса В); smsg_len -_длина посылаемого сообщения; rmsg_len -_максимальная длина ответа, который должен получить процесс А. Обратите внимание на то, что в сообщении будет передано не более, чем smsg_len байт и принято в ответе не более, чем rmsg_len байт, - это служит гарантией того, что буферы никогда не будут переполнены. 2.3.4.2. Использование функции Receive() Процесс В может принять запрос Send(), выданный процессом А, с помощью функции Receive() pid = Receive (0, msg, msg_len); Функция Receive() имеет следующие аргументы: pid - идентификатор процесса, пославшего сообщение (т.е. процесса А); 0 - (ноль) указывает на то, что процесс В готов принять сообщение от любого процесса; msg - буфер, в который будет принято сообщение; msg_len - максимальное количество байт данных, которое может поместиться в приемном буфере. В том случае, если значения smsg_len в функции Send() и msg_len в функции Receive() различаются, то количество передаваемых данных будет определяться наименьшим из них. 2.3.4.3. Использование функции Reply() После успешного приема сообщения от процесса А процесс В должен ответить ему, используя функцию Reply() Reply (pid, reply, reply_len) Функция Reply имеет следующие аргументы: pid - идентификатор процесса, которому направляется ответ (т.е. процесса А); reply - буфер ответа; reply_len - длина сообщения, передаваемого в ответе. Если значения reply_len в функции Reply() и rmsg_len в функции Send() различаются, то количество передаваемых данных определяется наименьшим из них. 2.3.5. Reply-управляемая передача сообщений Пример передачи сообщений, который мы только что рассмотрели, иллюстрирует наиболее типичный способ передачи, при котором обслуживающий процесс находится в RECEIVE-блокированом состоянии, ожидая запроса от другого процесса на выполнение какой-либо работы. Этот способ передачи сообщений называется Send-управляемым, при котором процесс, требующий обслуживания, инициирует работу, посылая сообщение, а обслуживающий процесс завершает работу, выдавая ответ на принятое сообщение. Существует еще и другой, менее распространенный, чем Send-управляемый, но в отдельных случаях более предпочтительный способ передачи сообщений, а именно_-_Reply-управляемый, при котором работа инициируется функцией Reply(). В соответствии с этим способом "рабочий" процесс посылает сообщение обслуживающему процессу, указывая на то, что он готов к работе. Обслуживающий процесс фиксирует, что "рабочий" процесс послал ему сообщение, но не отвечает ему немедленно. Через некоторое время обслуживающий процесс может ответить "рабочему" процессу. "Рабочий" процесс выполняет свою работу, а затем, завершив ее, посылает обслуживающему процессу сообщение, содержащее результаты. 2.3.6. Дополнительная информация Ниже приведена дополнительная информация о передаче сообщений, которую полезно знать. Данные, передаваемые в сообщении, находятся в процессе-отправителе до тех пор, пока получатель не будет готов к обработке сообщения. Сообщение не копируется в ядро. Это обеспечивает сохранность данных, так как процесс-отправитель остается SEND-блокированным и не может случайным образом модифицировать данные сообщения. При выдаче запроса Reply() данные, содержащиеся в ответном сообщении, передаются от отвечающего процесса REPLY-блокированному процессу за одну операцию. Функция Reply() не блокирует отвечающий процесс, так как REPLY-блокированный процесс разблокировывается сразу после того, как данные скопируются в его адресное пространство. Процессу-отправителю нет никакой необходимости "знать" что-либо о состоянии процесса-получателя, которому он посылает сообщение. В том случае, если процесс-получатель будет не готов к приему сообщения, то процесс-отправитель после отправления сообщения просто перейдет в SEND-блокированное состояние. При необходимости любой процесс может посылать сообщение нулевой длины, ответ нулевой длины, либо то и другое. С точки зрения разработчика выдача запроса Send() обслуживающему процессу - это практически то же самое, что и обращение к библиотеке подпрограмм. В обоих случаях разработчик формирует некоторые наборы данных, а затем выдает Send() или обращается к библиотечной функции. После этого, между двумя определенными точками программы Receive() и Reply()_-_в одном случае, либо между входом функции и оператором return_-_в другом, управление передается сервисным программам, при этом ваша программа ожидает завершения их выполнения. После того как сервисные программы отработали, ваша программа "знает", где находятся результаты их работы, и может затем анализировать коды ошибок, обрабатывать результаты и т.д. Несмотря на это кажущееся сходство, процесс передачи сообщения намного сложнее обычного вызова библиотечной функции. Например, Send() может по сети обратиться к другой машине, где действительно будет выполняться сервисная программа. Кроме того, может быть организована параллельная обработка данных без создания нового процесса. Обслуживающий процесс выдает Reply(), позволяя вызывающему процессу продолжать выполняться, и затем продолжает свою работу. Несколько процессов могут посылать сообщения одному процессу. Обычно процесс-получатель принимает сообщения в порядке их поступления, однако, может быть установлен режим приема сообщений в порядке приоритетов процессов-отправителей, представленный на рис.5. +------+---------->+-------------->+------------->+------------+ +------+ +-------+ +-------+ | обслужива- | | | | | ющий | +-------------+ +-------------+ +-------------+| процесс | |обслуживаемый| |обслуживаемый| |обслуживаемый|+------------+ |процесс Е | |процесс D | | процесс С | +-------------+ +-------------+ +-------------+ SEND-блокирован SEND-блокирован SEND-блокирован +-------------+ +-------------+ |обслуживаемый| |обслуживаемый| |процесс А | |процесс В | +-------------+ +-------------+ REPLY-блокирован REPLY-блокирован Обслуживающий процесс принял сообщения от обслуживаемых процессов А и В, но не ответил на них. Сообщения от процессов C, D, E еще не приняты. Риc. 5 2.3.7. Дополнительные возможности передачи сообщений В системе QNX имеются функции, предоставляющие дополнительные возможности передачи сообщений, а именно: - условный прием сообщений; - чтение и запись части сообщения; - передача составных сообщений. 2.3.7.1. Условный прием сообщений Обычно для приема сообщения используется функция Receive(). Этот способ приема сообщений в большинстве случаев является наиболее предпочтительным. Однако, иногда процессу требуется предварительно "знать", было ли ему послано сообщение, чтобы не ожидать поступления сообщения в RECEIVE-блокированном состоянии. Например, процессу требуется обслуживать несколько высокоскоростных устройств, не способных генерировать прерывания, и кроме того, процесс должен отвечать на сообщения, поступающие от других процессов. В этом случае используется функция Creceive(), которая считывает сообщение, если оно становится доступным, или немедленно возвращает управление процессу, если нет ни одного отправленного сообщения. ВНИМАНИЕ. По возможности следует избегать использования функции Creceive(), так как она позволяет процессу непрерывно загружать процессор на соответствующем приоритетном уровне. 2.3.7.2. Чтение или запись части сообщения В некоторых случаях предпочтительнее считывать или записывать только часть сообщения для того, чтобы использовать буфер, уже выделенный для сообщения, и не заводить рабочий буфер. Например, администратор ввода/вывода может принимать для записи сообщения, состоящие из заголовка фиксированной длины и переменного количества данных. Заголовок содержит значение количества байт данных (от 0 до 64 Кбайт). Администратор ввода/вывода может принимать сначала только заголовок, а затем, используя функцию Readmsg(), считывать данные переменной длины в соответствующий буфер. Если количество посылаемых данных превышает размер буфера, администратор ввода/вывода может вызывать функцию Readmsg() несколько раз, передавая данные по мере освобождения буфера. Аналогично, функцию Writemsg() можно использовать для сбора и копирования данных в буфер отправителя по мере его освобождения, снижая таким образом требования к размеру внутреннего буфера администратора ввода/вывода. 2.3.7.3. Передача составных сообщений До сих пор мы рассматривали сообщения как единый пакет байтов. Однако, как правило, сообщения состоят из нескольких дискретных частей. Например, сообщение может иметь заголовок фиксированной длины, за которым следуют данные переменной длины. Для того, чтобы части сообщения эффективно передавались и принимались без копирования во временный рабочий буфер, составное сообщение может формироваться в нескольких раздельных буферах. Этот метод позволяет администраторам ввода/вывода системы QNX Dev и Fsys, обеспечивать высокую производительность. Для работы с составными сообщениями используются следующие функции: Creceivemx() Readmsgmx() Receivemx() Replymx() Sendmx() Writemsgmx() На рис. 6 представлено составное сообщение, описанное с помощью управляющей структуры mx. Ядро ассемблирует части сообщения в единый поток данных. +------------------------------------------------------------------+ | часть 1|часть2| ................................. | часть n | +------------------------------------------------------------------+   сообщение  | +-------------------+ | +---------------+ | +--------------+ +-+------------+-----+ | | . | 1 | | +--------------+-----| | | . 2 | | +--------------------| | ~ . ~ | ~ . ~ | +--------------------| | | n .-+----+ +--------------------+ Рис. 6 2.3.8. Зарезервированные коды сообщений Все сообщения в системе QNX начинаются с 16-битового слова, которое называется кодом сообщения. Системные процессы QNX используют следующие коды сообщений: 0X0000 - 0X00FF - сообщения Администратора процессов; 0X0100 - 0X01FF - сообщения ввода/вывода (для всех обслуживающих программ); 0X0200 - 0X02FF - сообщения Администратора файловой системы; 0X0300 - 0X03FF - сообщения Администратора устройств; 0X0400 - 0X04FF - сообщения Сетевого администратора; 0X0500 - 0X0FFF - зарезервировано для системных процессов, которые могут появиться в будущем. 2.4. Связь между процессами посредством proxy Proxy представляет собой форму неблокирующей передачи сообщений, специально предназначенную для оповещения о событиях, при которых процесс-отправитель не нуждается во взаимодействии с процессом-получателем. Единственной функцией proxy является посылка фиксированного сообщения процессу, создавшему proxy. Так же, как и сообщения, proxy работают по всей сети. Благодаря использованию proxy, процесс или обработчик прерываний может послать сообщение другому процессу, не блокируясь и не ожидая ответа. Ниже приведены некоторые примеры использования proxy: -_процесс оповещает другой процесс о наступлении некоторого события, не желая при этом оставаться SEND-блокированным до тех пор, пока получатель не выдаст Receive() и Reply(); -_процесс посылает данные другому процессу, но не требует ни ответа, ни другого подтверждения о том, что получатель принял сообщение; -_обработчик прерываний оповещает процесс о том, что некоторые данные доступны для обработки. Proxy создаются с помощью функции qnx_proxy_attach(). Любой процесс или обработчик прерываний, которому известен идентификатор proxy, может воспользоваться функцией Trigger() для того, чтобы выдать заранее определенное сообщение. Запросами Trigger() управляет ядро. Процесс proxy может быть запущен несколько раз: выдача сообщения происходит каждый раз при его запуске. Процесс proxy может накопить в очереди для выдачи до 65535 сообщений. +--------------+ +------------------------+ | обслуживаемый| .---> .---> .---> | процесс | | процесс | 3 2 1 | proxy | +--------------+ +------------------------+ +------------------------+ | "законсервированное" | | сообщение | 3 +------------------------+ +------------------------+ | "законсервированное" | 2 | сообщение | +------------------------+ +------------------------+ | "законсервированное" | 1 | сообщение | +------------------------+  +------------------------+ | обслуживающий | | процесс | +------------------------+ Обслуживаемый процесс трижды обратился к proxy, в результате чего обслуживающий процесс получил три "законсервированных" сообщения от proxy. Рис. 7 2.5. Связь между процессами посредством сигналов Связь посредством сигналов представляет собой традиционную форму асинхронного взаимодействия, используемую в различных операционных системах. В системе QNX поддерживается большой набор POSIX-совместимых сигналов, специальные QNX-сигналы, а так же исторически сложившиеся сигналы, используемые в некоторых версиях системы UNIX. 2.5.1. Генерация сигналов Сигнал выдается процессу при наступлении некоторого заранее определенного для данного сигнала события. Процесс может выдать сигнал самому себе. ----------------------------------------------------------------- Если вы хотите Используйте ----------------------------------------------------------------- Сгенерировать сигнал из интерпретатора Shell Утилиты kill или slay Сгенерировать сигнал из процесса Функции kill() или raise() ----------------------------------------------------------------- 2.5.2. Прием сигналов В зависимости от того, каким образом был определен способ обработки сигнала, возможны три варианта его приема: 1. Если процессу не предписано выполнять каких-либо специальных действия по обработке сигнала, то по умолчанию поступление сигнала прекращает выполнение процесса; 2. Процесс может проигнорировать сигнал. В этом случае выдача сигнала не влияет на работу процесса (обратите внимание на то, что сигналы SIGCONT, SIGKILL и SIGSTOP не могут быть проигнорированы при обычных условиях); 3. Процесс может иметь обработчик сигнала, которому передается управление при поступлении сигнала. В этом случае говорят, что процесс может "ловить" сигнал. Фактически такой процесс выполняет обработку программного прерывания. Данные с сигналом не передаются. Интервал времени между генерацией и выдачей сигнала называется задержкой. В данный момент времени для одного процесса могут быть задержаны несколько разных сигналов. Сигналы выдаются процессу тогда, когда планировщик ядра переводит процесс в состояние готовности к выполнению. Порядок поступления задержанных сигналов не определен. 2.5.3. Перечень сигналов В приведенной ниже таблице содержится перечень всех сигналов с указанием обрабатывается сигнал или игнорируется, а также действий, выполняемых по умолчанию при поступлении сигнала. ------------------------------------------------------------------- Сигнал Обрабатывается Действие по Описание умолчанию ------------------------------------------------------------------- Стандартные сигналы SIGABRT да Завершить процесс Сигнал ненормального завершения, такой же, какой выдается функцией abort() SIGALARM да Завершить процесс Сигнал истечения времени, такой же, какой выдает- ся функцией alarm() SIGFPE** да Завершить процесс Ошибочная арифметическая операция (целочисленная или с плавающей точкой), например, деление на 0 или операция, приводящая к переполнению SIGHUP да Завершить процесс Гибель инициатора сессии, либо зависание на управляющем терминале SIGILL да Завершить процесс Обнаружение аппаратной ошибки SIGINT да Завершить процесс Интерактивный сигнал внимания () SIGKILL да Завершить процесс Сигнал завершения (следует использовать только в чрезвычайных ситуациях) SIGPIPE да Завершить процесс Попытка записи в канал при отсутствии процессов, читающих из него SIGOUIT да Завершить процесс Интерактивный сигнал завершения SIGSEGV** да Завершить процесс Обнаружение неправильной ссылки в памяти SIGTERM да Завершить процесс Сигнал завершения SIGUSR1 да Завершить процесс Зарезервирован как 1-й сигнал, определяемый приложением SIGUSR2 да Завершить процесс Зарезервирован как 2-й сигнал, определяемый приложением ------------------------------------------------------------------- Сигнал Обрабатывается Действие по Описание умолчанию ------------------------------------------------------------------- Сигналы, управляющие работой процессов SIGHLD да Игнорировать сигнал Завершить порожденный процесс SIGCONT нет Продолжить процесс Продолжить, если данный процесс задержан задержки; игнориро- вать сигнал, если этот процесс не за- держан SIGSTOP* нет Приостановить Сигнал задержкки процесса процесс SIGTSTP* нет Игнорировать сигнал Не поддерживается в QNX SIGTTIN нет Игнорировать сигнал Не поддерживается в QNX SIGTTOU нет Игнорировать сигнал Не поддерживается в QNX Специальные сигналы QNX SIGBUS** да Завершить процесс Указывает на ошибку четности в памяти (специальная интерпретация QNX) SIGDEV да Завершить процесс Генерируется, когда в Администраторе устройств возникает важное и запрашиваемое событие SIGPWR да Завершить процесс Мягкая перезагрузка по нажатию клавиш < CTRL > < ALT > < SHIFT > или по выполнению утилиты shutdown Исторически оставшиеся сигналы UNIX SIGIOT*** да Завершить процесс Команда IOT SIGSIS*** да Завершить процесс Некорректный аргумент в системном вызове SIGWINCH*** да Завершить процесс Смена окна SIGURG*** да Завершить процесс Выполнение необходимого условия SIGPOLL*** да Завершить процесс Выполнение выбранного события SIGEMT*** да Завершить процесс Команда EMT (эмулятор внутреннего прерывания) SIGTRAP да Завершить процесс Неподдерживаемое программное прерывание ------------------------------------------------------------------ Условные обозначения: * -_обслуживающий процесс может "защитить" себя от этого сигнала посредством функции qnx_pflags(). Для этого обслуживающий процесс должен иметь уровень суперпользователя; ** -_процесс завершается в случае возникновения второго сбоя во время обработки процессом первого; *** -_этот сигнал оставлен для исторической совместимости с некоторыми версиями системы UNIX, он не генерируется никакими компонентами системы QNX. 2.5.4. Определение способа обработки сигнала Для задания способа обработки сигнала следует воспользоваться функцией ANSI C signal() или функцией POSIX sigaction(). Функция sigaction() предоставляет больше возможностей по управлению средой обработки сигнала. Способ обработки сигнала можно изменить в любое время. Если установить обработчику режим игнорирования сигналов, то все задержанные сигналы будут немедленно отменены. 2.5.5. Обработка сигналов Отметим некоторые особенности работы процессов, которые "ловят" сигналы с помощью обработчика сигналов. Обработчик сигналов аналогичен программному прерыванию. Он выполняется асинхронно с другими программами процесса. Следовательно, обработчик сигналов может быть запущен во время выполнения любой функции в программе (включая библиотечные функции). Если процессу не требуется возврата управления от обработчика сигналов в прерванную точку, то в этом случае в обработчике сигналов может быть использована функция siglongjmp() или longjmp(). Причем siglongjmp() предпочтительнее, т.к. в случае использования longjmp() сигнал остается блокированным. 2.5.6. Рекомендуемые функции для обработчиков сигналов Приведенные ниже библиотечные функции стандартов POSIX и ANSI C рекомендуются к использованию в обработчиках сигналов. Не следует пытаться использовать другие библиотечные функции, так как результаты этого могут быть непредсказуемы. Функции пользователя, используемые в вашей программе, должны быть обязательно повторно входимыми. _exit() getegid() rmdir() tcdrain()____ access() geteuid() setgid() tcflow()_____ alarm() getgid() setpgid() tcflush()____ cfgetispeed() getgroups() setsid() tcgetattr()__ cfgetospeed() getpgrp() setnid() tcgetpgrp()__ cfsetispeed() getpid() sigaction() tcsendbreak() cfsetospeed() getppid() sigaddset() tcsetattr()__ chdir() getuid() sigdelset() tcgetgrp()___ chmod() kill() sigemptyset() time()_______ chown() link() sigfillset() times()______ close() lseek() sigismember() umask()______ creat() mkdir() signal() uname()______ dup2 mkfifo() sigpending() unlink()_____ dup() open() sigprocmask() ustat()______ execle() pathconf() sigsuspend() utime()______ execve() pause() slup() wait()_______ fcntl() pipe() stat() waitpid()____ fork() read() sysconf() write()______ fstat() rename()________________________________________________________________________________ 2.5.7. Блокировка сигналов Иногда может потребоваться временно задержать выдачу сигнала, не изменяя при этом способа его обработки. В системе QNX имеется набор функций, которые позволяют блокировать выдачу сигналов. После разблокировки сигнал выдается программе. Во время работы обработчика сигналов QNX автоматически блокирует обрабатываемый сигнал. Это означает, что не требуется организовывать вложенные вызовы обработчика сигналов. Каждый вызов обработчика сигналов непрерываем остальными сигналами данного типа. При нормальном возврате управления от обработчика, сигнал автоматически разблокируется. ВНИМАНИЕ. В некоторых версиях системы UNIX работа с обработчиком сигналов организована некорректно, так как в них не предусмотрена блокировка сигналов. В результате в некоторых приложениях, работающих под управлением UNIX, используется функция signal() внутри обработчика прерываний с целью "перевзвода" обработчика. В этом случае может возникнуть одна из двух аварийных ситуаций. Во-первых, если другой сигнал поступает, во время работы обработчика, но вызова функции signal() еще не было, то программа будет снята с обработки. Во-вторых, если сигнал поступает сразу же после вызова обработчиком функции signal(), то обработчик будет запускаться рекурсивно. В QNX выполняется блокировка сигналов, поэтому указанные выше проблемы не могут возникнуть. Нет необходимости вызывать signal() из обработчика. Если требуется выйти из любой точки обработчика, то следует воспользоваться функцией siglongjmp(). 2.5.8. Сигналы и сообщения Существует важная взаимосвязь между сигналами и сообщениями. Если при генерации сигнала ваш процесс окажется SEND-блокированным или RECEIVE-блокированным (причем имеется обработчик сигналов), то будут выполняться следующие действия: 1) процесс разблокировывается; 2) выполняется обработка сигнала; 3) функции Send() или Receive() возвращают управление с кодом ошибки. Если процесс был SEND-блокированным, то проблемы не возникает, так как получатель не получит сообщение. Но если процесс был REPLY-блокированным, то неизвестно, было обработано отправленное сообщение или нет, а следовательно неизвестно, нужно ли еще раз выдавать Send(). Процесс, выполняющий функции сервера (т.е. принимающий сообщения), может запрашивать уведомления о том, когда обслуживаемый процесс выдаст сигнал, находясь в REPLY-блокированном состоянии. В этом случае обслуживаемый процесс становится SIGNAL-блокированным с задержанным сигналом, и обслуживающий процесс принимает специальное сообщение, описывающее тип сигнала. Обслуживающий процесс может выбрать одно из следующих действий: -_нормально завершить первоначальный запрос: отправитель будет уведомлен о том, что сообщение было обработано надлежащим образом; -_освободить все закрепленные ресурсы и возвратить управление с кодом ошибки, указывающим на то, что процесс был разблокирован сигналом: отправитель получит чистый код ошибки. Когда обслуживающий процесс сообщает другому процессу, что он SIGNAL-блокирован, сигнал выдается немедленно после возврата управления функцией Send(). 2.6. Связь между процессами в сети 2.6.1. Виртуальные каналы В системе QNX приложение может взаимодействовать с процессом, выполняющимся на другом компьютере сети, так же как с процессом, выполняющемся на своем компьютере. В самом деле, с точки зрения приложения нет никакой разницы между локальными и удаленными ресурсами. Такая высокая степень прозрачности обеспечивается благодаря использованию виртуальных каналов, которые являются путями, по которым Сетевой администратор передает сообщения и сигналы по всей сети. Виртуальные каналы (ВК) способствуют эффективному использованию ресурсов во всей сети QNX по нескольким причинам: -_при создании виртуального канала имеется возможность задать работу с сообщениями определенной длины: это означает, что вы можете распределить ресурсы для обработки сообщения. Тем не менее, если потребуется послать сообщение, длина которого превышает максимально заданную, виртуальный канал автоматически изменит установленный максимальный размер буфера в соответствии с длиной передаваемого сообщения; -_если два процесса, находящиеся на разных узлах, взаимодействуют между собой более, чем через один виртуальный канал, виртуальные каналы разделяются во времени, так как между процессами существует только один реальный виртуальный канал. Эта ситуация часто возникает, когда процесс обращается к нескольким файлам удаленной файловой системы; -_если процесс подключается к существующему разделенному виртуальному каналу и запрашивает размер буфера больший, чем тот, который используется в данное время, размер буфера автоматически увеличивается; -_когда процесс завершается, все связанные с ним виртуальные каналы освобождаются. 2.6.2. Виртуальные процессы Процесс-отправитель отвечает за установку виртуального канала между собой и процессом, с которым устанавливается связь. Для этого процесс-отправитель обычно вызывает функцию qnx_vc_attach(). При этом, кроме создания виртуального канала, на каждом конце канала создается виртуальный процесс с идентификатором - VID. Для каждого процесса на обоих концах виртуального канала VID представляет собой идентификатор удаленного процесса, с которым устанавливается связь. Процессы связываются друг с другом посредством VID. Например, на рис. 8 виртуальный канал соединяет процессы PID1 и PID2. На узле 20, где находится PID1, VID2 представляет PID2. На узле 40, где находится PID2, VID1 представляет PID1. PID1 и PID2 могут относиться к виртуальному процессу на своем узле, как к любому другому локальному процессу: посылать и принимать сообщения, выдавать сигналы, ожидать и т.п. Так, например, PID1 может послать сообщение к VID на своем конце виртуального канала, которое будет передано по сети к VID на другом конце виртуального канала, представляющему там PID1. Там VID1 передает сообщение PID2. узел 20 узел 40 +------------------------------+ +------------------------------+ | +--------+ send() +--------+ | | +---------+ send()+-------+| | | PID1 +------->| VID2 +-+---+-->| VID1 +------>| PID2 || | +--------+ +--------+ | | +---------+ +-------+| +------------------------------+ +------------------------------+ Связь по сети осуществляется посредством виртуальных каналов. Когда процесс PID1 посылает сообщение VID2 запрос send проходит по виртуальному каналу, в результате чего PID2 получает сообщение от VID1. Рис. 8 Каждый VID обеспечивает соединение, которое содержит следующую информацию: - локальный pid; - удаленный pid; - удаленный nid (идентификатор узла); - удаленный vid. Вряд ли вам придется работать с виртуальным каналом напрямую. Если приложению требуется, например, получить доступ к удаленному ресурсу ввода/вывода, то виртуальный канал создается вызываемой библиотечной функцией open(). Приложения непосредственно не участвуют в создании или использовании виртуального канала. Если приложение определяет нахождение обслуживающего его процесса с помощью функции qnx_name_locate(), то виртуальный канал создается автоматически при вызове функции. Для приложения виртуальный канал просто отождествляется с PID. Более подробная информация о функции qnx_name_locate() содержится в подразделе 3.4. 2.6.3. Отключение виртуальных каналов Существует несколько причин, по которым процесс не может осуществлять связь по установленным виртуальным каналам, а именно: - произошло отключение питания компьютера, на котором выполняется процесс; - был отсоединен кабель сети от компьютера; - был завершен удаленный процесс, с которым установлена связь. Любая из этих причин может препятствовать передаче сообщений по виртуальному каналу. Необходимо фиксировать эти ситуации для выполнения приложением необходимых действий с целью корректного завершения работы, в противном случае, отдельные ресурсы могут оказаться постоянно занятыми. На каждом узле Администратор процессов проверяет целостность виртуального канала. Это делается следующим образом: 1)_Каждый раз при успешной передаче по виртуальному каналу, обновляется временная метка, связанная с данным виртуальным каналом, для фиксации времени последней активности; 2)_Через интервалы времени, устанавливаемые при инсталляции, Администратор процессов просматривает каждый виртуальный канал. В том случае, если в виртуальном канале нет активности, Администратор процессов посылает сетевой пакет проверки целостности канала Администратору процессов другого узла; 3)_В том случае, если ответ не получен, или зафиксирован сбой, виртуальный канал помечается, как сбойный. Далее предпринимается ряд действий, определенных при инсталляции для восстановления связи; 4)_Если попытки восстановления закончились безуспешно, виртуальный канал "отключается". Все процессы, блокированные на данном канале, переходят в состояние ГOTOB (READY). (Процессы анализируют возвращаемый код сбоя виртуального канала.) Для управления параметрами, связанными с проверкой целостности виртуального канала, используется утилита netpoll. 2.7. Планирование процессов 2.7.1. Когда выполняется планирование Планировщик ядра запускается в следующих случаях: -_после разблокировки процесса; -_по истечении временного кванта для выполняющегося процесса; -_после выгрузки выполняющегося процесса. 2.7.2. Приоритеты процессов В системе QNX каждому процессу присваивается приоритет. Планировщик выбирает для выполнения процессы, находящиеся в состоянии ГОТОВ, в соответствии с их приоритетами. (Центральный процессор может использовать только процесс, находящийся в состоянии ГОТОВ.) Для выполнения выбирается процесс, имеющий наивысший приоритет. На рис. 9 представлен пример выполнения процессов в соответствии с приоритетом. Очередь процессов в состоянии ГОТОВ +----------+31 | | | | ~ ~ ~ ~ | | активен | | | +----------|  | | +-------+ +--------+ +-------+ | .----+10--->| A +-------->| B +------->| C | +----------| +-------+ +--------+ +-------+ +----------| +----------| +----------| +----------| | | +-------+ | .----+-5-->| D | +------+ +------+ +----------| +-------+ | G | ... | Z | +----------| +------+ +------+ +----------| +----------| | | +--------+ +--------+ | .----+0-->| E +--------->| F | +----------+ +--------+ +--------+ Очередь процессов (A-F), находящихся в состоянии ГОТОВ. Остальные процессы (G-Z) блокированы. В данный момент выполняется процесс А. Процессы А, B и С имеют высший приоритет, поэтому они будут разделять процессорное время в соответствии с установленным алгоритмом планирования. Рис. 9 Процессам присваиваются приоритеты в диапазоне от 0 (низший) до 31 (высший). По умолчанию процесс наследует приоритет от породившего его процесса; обычно он равен 10 для приложений, запускаемых из интерпретатора Shell. ----------------------------------------------------------------- Если вы хотите Используйте ---------------------------------------------------------------- Определить приоритет процесса Функцию getprio() Задать приоритет процессу Функцию setprio() ----------------------------------------------------------------- 2.7.3. Методы планирования Для удовлетворения потребностей разных приложений в системе QNX реализованы три метода планирования: -_планирование по принципу простой очереди (первым пришел_-_первым обслужен); -_круговой метод планирования; -_адаптивное планирование. Каждый процесс в системе может выполняться, используя любой из этих методов. Они эффективны применительно к одному процессу, а не ко всем процессам на узле. Запомните, что данные методы планирования используются только тогда, когда два или более процессов, разделяющих один и тот же приоритет, находятся в состоянии ГОТОВ (т.е. процессы непосредственно конкурируют друг с другом). Если в состояние ГОТОВ переходит процесс, имеющий более высокий приоритет, он немедленно выгружает все процессы с меньшим приоритетом. На рис. 10 три процесса, имеющие одинаковые приоритеты, находятся в состоянии ГОТОВ. Если процесс А блокируется, процесс В начнет выполняться. +-----------+ | | | | | | ~ ~ | | | | +-----------| | | +-------+ +------+ | .-----+------->| B +------>| C | +-----------| +-------+ +------+ | | | | +-----+ | | | A | | | +-----+ +-----------+ Рис. 10 Метод планирования наследуется от порождающего процесса, однако, он может быть изменен. ----------------------------------------------------------------- Если вы хотите Используйте ----------------------------------------------------------------- Определить метод планирования для процесса Функцию getscheduler() Установить метод планирования для процесса Функцию setscheduler() ----------------------------------------------------------------- 2.7.3.1. Планирование по принципу простой очереди При планировании по принципу простой очереди процесс, выбранный для выполнения, продолжает работать до тех пор, пока он: - не передаст управление сам (например, блокируется); - не будет снят с выполнения (выгружен из памяти) процессом с более высоким приоритетом. Два процесса, выполняющихся с одинаковыми приоритетами, могут использовать планирование по принципу простой очереди во избежание взаимных "столкновений" при обращении к разделяемому ресурсу. Например, если они разделяют сегмент памяти, то каждый из двух процессов может обновлять сегмент без использования некоторых форм семафоров. 2.7.3.2. Круговой метод планирования При круговом методе планирования процесс, выбранный для выполнения, продолжает работать до тех пор, пока он: - не передаст управления сам; - не будет снят с выполнения (выгружен из памяти) процессом с более высоким приоритетом; - не истечет его квант времени (timeslice). Квант времени - это единица временного интервала, закрепляемая за каждым процессом. По истечении кванта времени, процесс выгружается, и управление передается процессу, находящемуся на том же уровне приоритета в состоянии ГОТОВ. Квант времени равен 100 миллисекундам. ВНИМАНИЕ. За исключением квантования времени круговой метод планирования идентичен планированию по принципу простой очереди. 2.7.3.3. Адаптивное планирование При адаптивном планировании процесс ведет себя следующим образом: -_по истечении кванта времени (при условии, что процесс не блокировался), его приоритет уменьшается на 1, если другой процесс с таким же приоритетом находится в состоянии ГОТОВ. Это называется понижением приоритета; -_если процесс с пониженным приоритетом не выполняется в течение одной секунды, его приоритет повышается на 1 (процесс никогда не может повысить приоритет выше начального); -_если процесс блокируется, ему немедленно возвращается начальный приоритет. Адаптивное планирование можно использовать в среде, где интенсивно выполняющиеся фоновые процессы разделяют компьютер с пользовательскими процессами, работающими в диалоговом режиме. Адаптивное планирование обеспечит интенсивно выполняющимся процессам достаточный доступ к центральному процессору и сохранит малое время отклика для процессов, работающих в диалоговом режиме. Программы, запущенные из интерпретатора Shell, используют по умолчанию адаптивный метод планирования. Методы планирования представлены на рис. 11. Простая Круговое Адаптивное очередь +----+ +----+ +---+ процессу | | ничего не проис- | | процесс А уста- | | А понижа- ~ ~ ходит,процесс А ~ ~ навливается в ко- ~ ~ ется при- ~ ~ выполняется, пока ~ ~ нец списка; про- ~ ~ оритет; | | не заблокируется | | цесс В выполня- | | процесс В | | | | ется | | выполня- | | | | | | ется +----| +--+ +--+ +--+ +----| +--+ +--+ +--+ +---| +-+ +-+ | .--+->|A +->|B +->|C | | .--+->|B +->|C +->|A | | .-+->|B|->|C| +----| +--+ +--+ +--+ +----| +--+ +--+ +--+ +---| +-+ +-+ | | | | | .-+->+-+ | | | | +---| |A| | | | | | | +-+ | | | | | | +----+ +----+ +---+ Процесс А продолжает выполняться до истечения кванта времени, после чего в зависимости от алгоритма планирования определяется, что произойдет дальше. Рис. 11 2.7.3.4. Приоритет, управляемый клиентом В системе QNX в большинстве случаев взаимодействия между процессами используется модель "клиент-сервер". Серверы (обслуживающие процессы) выполняют некоторые сервисные функции, а клиенты (обслуживаемые процессы) посылают сообщения к этим серверам, запрашивая обслуживание. В общем случае серверы более "надежны и долговечны", чем клиенты. Обычно количество клиентов превосходит количество серверов. В результате сервер, как правило, выполняется с приоритетом, превосходящим приоритеты всех своих клиентов. При этом может использоваться любой из описанных выше методов планирования, однако, круговой метод предпочтительнее. Если низкоприоритетный клиент посылает сообщение серверу, то по умолчанию его запрос будет обрабатываться сервером с более высоким приоритетом. Это косвенно повышает приоритет клиента, так как именно запрос клиента заставил работать сервер. Если сервер работает на коротком отрезке времени, то этим можно пренебречь. Если же сервер работает более длительное время, то низкоприоритетный клиент может негативно влиять на другие процессы, имеющие приоритеты выше, чем у клиента, но ниже, чем у сервера. Для разрешения этой проблемы, серверу можно устанавливать приоритет, соответствующий приоритету клиента, который послал ему сообщение. Когда сервер получает сообщение, его приоритет становится равным приоритету клиента. Обратите внимание на то, что меняется только приоритет, а метод планирования остается тем же. Если при работе сервера поступает другое сообщение, то приоритет сервера увеличивается в том случае, если приоритет нового клиента окажется выше приоритета сервера. В результате новый клиент "выравнивает" приоритет сервера под свой, позволяя ему закончить выполнение текущего запроса и перейти к выполнению вновь поступившего. Если этого не сделать, то приоритет нового клиента понизился бы, так как он заблокировался бы на низкоприоритетном сервере. Если вы выбираете для вашего сервера приоритеты, управляемые клиентом, то вам следует также позаботиться и о том, чтобы сообщения доставлялись в порядке приоритетов (а не в порядке времени поступления). Для установки приоритета, управляемого клиентом, воспользуйтесь функцией qnx_pflags() qnx_pflags(~0, _PPF_PRIORITY_FLOAT | _PPF_PRIORITY_REC, 0, 0); 2.8. О работе в реальном времени Как бы нам этого не хотелось, но компьютер не может иметь бесконечное быстродействие. В системе реального времени крайне важно, использовать все циклы работы центрального процессора. Также важно минимизировать интервал времени между возникновением внешнего события и фактическим началом выполнения программы, реализующей ответную реакцию на это событие. Это время называется задержкой или временем ожидания (latency). В системе QNX можно определить несколько типов задержки. 2.8.1. Задержка прерывания Задержка прерывания - это интервал времени между приемом аппаратного прерывания и началом выполнения первой команды обработчика данного прерывания. В системе QNX все прерывания открыты все время, поэтому задержка прерывания обычно незначительна. Но некоторые критические программы требуют, чтобы на время их выполнения прерывания были закрыты. Максимальное время закрытия прерывания обычно определяет худший случай задержки прерывания; следует отметить, что в системе QNX это время очень мало. На рис._12 представлена диаграмма обработки аппаратного прерывания соответствующим обработчиком прерываний. Обработчик прерываний либо просто возвращает управление процессу, либо возвращает управление и вызывает "срабатывание" proxy. возникновение запуск завершение прерванный про- прерывания обработчика работы цесс продолжает прерываний обработчика выполнение | | | | |     +---------------------------------------------------------------->T | | 19,0 мксек | | 17,4 мксек| +---------------+ +-----------+ Til Tiret | | +--------------------+ Tint Til - время задержки прерывания; Tint - время обработки прерывания; Tiret - время завершения прерывания. Обработчик прерываний нормально отрабатывает. Времена даны для процессора 386, 20 МГц в защищенном режиме. Рис. 12 На диаграмме, приведенной выше, задержка прерывания (Til) представляет собой минимальную задержку, для случая, когда во время возникновения прерывания все прерывания были открыты. В худшем случае задержка равна этому времени плюс наибольшее время работы процесса QNX, когда прерывания закрыты. 2.8.2. Задержка планирования В некоторых случаях низкоприоритетный обработчик аппаратных прерываний должен планировать выполнение высокоприоритетных процессов. В этом случае обработчик прерываний возвращает управление и вызывает срабатывание proxy. Это и есть вторая форма задержки - задержка планирования, которую мы рассмотрим ниже. Задержка планирования - это время между завершением работы обработчика прерываний и началом выполнения первой команды управляющего процесса. Обычно это интервал времени, который требуется для сохранения контекста процесса, выполняющегося в данный момент времени, и восстановления контекста управляющего процесса. Несмотря на то, что это время больше задержки прерывания, оно также остается небольшим в системе QNX. На рис. 13 представлена диаграмма задержки планирования. завершение работы запуск обработчика управляющего возникновение запуск прерываний процесса прерывания обработчика и "срабатывание" прерываний proxy | | | | | | | | |     +--------------------------------------------------------------->T | | 19,0 мксек | | 45,2 мксек | +---------------+ +--------------------+ Til | | Tsl +---------+ Tint Til - время задержки прерывания; Tint - время обработки прерывания; Tsl - задержка планирования. Обработчик прерываний завершает работу и инициирует "срабатывание" proxy. Времена даны для процессора 386, 20 МГц в защищенном режиме. Рис. 13 Важно заметить, что большинство обработчиков прерываний завершают работу без инициирования "срабатывания" proxy. В большенстве случаев обработчик прерываний сам справляется со всеми аппаратными событиями. Выдача proxy для подключения управляющего процесса более высокого уровня происходит только при возникновении особых событий. Например, обработчик прерываний драйвера устройства с последовательным интерфейсом, передающий один байт данных аппаратуре, должен на каждое принятое прерывание на передачу запустить высокоуровневый процесс (Dev) только в том случае, если выходной буфер в итоге окажется пустым. 2.8.3. Вложенные прерывания Поскольку архитектура микрокомпьютера позволяет присваивать аппаратным прерываниям приоритеты, то высокоуровневые прерывания могут вытеснять низкоуровневые. Этот механизм полностью поддерживается в системе QNX. В предыдущих примерах описаны простейшие и наиболее обычные ситуации, когда поступает только одно прерывание. Практически такие же временные характеристики имеют прерывания с высшим приоритетом. При расчете наихудших временных показателей для низкоприоритетных прерываний следует учитывать время обработки всех высокоприоритетных прерываний, так как в системе QNX высокоприоритетные прерывания вытесняют низкоприоритетные. На рис. 14 представлен пример прерывания процесса. +-----..................................................-------+ | | +-----..................................................-------+ A +-------+ | | +-------+ +-------+ C | | +-------+ +-------....................------+ B | | +-------....................------+ Intx +------------------+ | | +------------------+ Inty   | | | | IRQx IRQy Выполняется процесс А. Прерывание IRQx запускает обработчик прерываний Intx, который вытесняется прерыванием IRQy и его обработчиком Inty. Inty вызывает "срабатывание" proxy, которое запускает процесс В, а Intx вызывает "срабатывание" proxy, запускающее процесс С. Рис. 14 3. АДМИНИСТРАТОР ПРОЦЕССОВ 3.1. Введение 3.1.1. Функции Администратора процессов Администратор процессов тесно связан с ядром операционной системы. Однако, несмотря на то, что он разделяет с ядром одно и то же адресное пространство (единственный из всех системных процессов), он выполняется как обычный процесс. Это означает, что Администратор процессов планируется к выполнению ядром и использует те же примитивы передачи сообщений для взаимодействия с другими процессами. Администратор процессов отвечает за создание в системе новых процессов и за управление ресурсами, связанными с процессом. Все эти функции реализуются посредством передачи сообщений. Например, создание выполняющимся процессом нового процесса осуществляется посредством посылки сообщения, содержащего подробную информацию о вновь создаваемом процессе. Поскольку передача сообщений распространяется на всю сеть, то можно легко создать новый процесс на другом узле, послав соответствующее сообщение Администратору процессов удаленного узла. 3.1.2. Примитивы создания процесса В системе QNX существует три примитива создания процесса: fork() exec() spawn() Примитивы fork() и exec() определены стандартом POSIX, а примитив spawn() реализован только в QNX. 3.1.2.1. fork() Примитив fork() порождает процесс, являющийся его точной копией. Новый процесс выполняется в том же адресном пространстве и наследует все данные порождающего процесса. 3.1.2.2. exec() Примитив exec() заменяет образ порождающего процесса образом нового процесса. Возврата управления из нормально отработавшего exec() не существует, т.к. образ нового процесса накладывается на образ порождающего процесса. В системах стандарта POSIX новые процессы обычно создаются без возврата управления порождающему процессу - сначала вызывается fork(), а затем из порожденного процесса - exec(). 3.1.2.3. spawn() Примитив spawn() создает новый процесс по принципу "отец"-"сын". Это позволяет избежать использования примитивов fork() и exec(), что ускоряет обработку и является более эффективным средством создания новых процессов. В отличие от fork() и exec(), которые по определению создают процесс на том же узле, что и порождающий процесс, примитив spawn() может создавать процессы на любом узле сети. 3.1.3. Что наследует процесс При создании процесса с помощью одного из трех описанных выше примитивов, он наследует многое от той программной среды, в которой выполнялся его "родитель". Конкретная информация представлена в следующей таблице. ------------------------------------------------------------------- Что наследуется fork() exec() spawn() ------------------------------------------------------------------- Идентификатор процесса Нет Да Нет Открытые файлы Да На выбор* На выбор Блокировка файлов Нет Да Нет Задержанные сигналы Нет Да Нет Маска сигнала Да На выбор На выбор Игнорируемые сигналы Да На выбор На выбор Обработчик сигналов Да Нет Нет Переменные среды Да На выбор На выбор Идентификатор сеанса Да Да На выбор Группа процесса Да Да На выбор Реальные идентификаторы Да Да Да группы и пользователя ( UID, GID ) Эффективные UID, GID Да На выбор На выбор Текущий рабочий каталог Да На выбор На выбор Маска создания файлов Да Да Да Приоритет Да На выбор На выбор Метод планирования Да На выбор На выбор Виртуальные каналы Нет Нет Нет Символические имена Нет Нет Нет Таймеры реального времени Нет Нет Нет ------------------------------------------------------------------- Примечание. * - вызывающий процесс может по необходимости выбрать - да или нет. 3.2. Жизненный цикл процесса Каждый процесс проходит следующие четыре фазы: 1. Создание; 2. Загрузку; 3. Выполнение; 4. Завершение. 3.2.1. Создание Создание процесса заключается в присвоении идентификатора процесса (ID) новому процессу и задании информации, определяющей программную среду нового процесса. Большая часть этой информации наследуется от "родителя" нового процесса (см. предыдущий параграф). 3.2.2. Загрузка Загрузка образов процессов выполняется загрузчиком "по цепочке". Загрузчик входит в состав Администратора процессов и выполняется под идентификатором нового процесса. Это позволяет Администратору процессов выполнять другие запросы при загрузке программ. 3.2.3. Выполнение Как только программный код загружен, процесс готов к выполнению; он начинает конкурировать с другими процессами, стремясь получить ресурсы центрального процессора. Обратите внимание на то, что процессы выполняются конкурентно вместе со своими "родителями". Кроме того, гибель "родителя" не вызывает автоматически гибель порожденных им процессов. 3.2.4. Завершение Процесс завершается одним из двух способов: - по сигналу, определяющему процесс завершения; - при возврате управления по функции exit()_-_явно, либо по функции main()_-_по умолчанию. Завершение включает в себя две стадии: 1. Администратор процессов инициирует выполнение завершения "по цепочке". Программа завершения входит в состав Администратора процессов и выполняется под идентификатором завершающегося процесса. При этом выполняется закрытие всех описателей открытых файлов и освобождаются: - все виртуальные каналы, которые имел процесс; - вся память, выделенная процессу; - все символические имена; - все номера основных устройств (только для администраторов ввода/вывода ); - все обработчики прерываний; - все proxy; - все таймеры. 2. После запуска программы завершения к породившему его процессу посылается уведомление о завершении процесса (эта фаза выполняется внутри Администратора процессов). Если породивший процесс не выдал wait() или waitpid(), то порожденный процесс становится так называемым "зомби"-процессом и не завершается до тех пор, пока породивший процесс не выдаст wait() или не завершится сам. Для того, чтобы не ждать завершения порожденного процесса, следует либо установить признак _SPAWN_NOZOMBIE в функциях qnx_spawn() или qnx_spawn_option(), либо в функции signal() задать для SIGCHLD признак SIG_IGN. В этом случае порожденные процессы при завершении не становятся "зомби"-процессами. Породивший процесс может ожидать завершения порожденного процесса на удаленном узле. Если процесс, породивший "зомби"-процесс завершается, то освобождаются все ресурсы, связанные с "зомби". Если процесс завершается по сигналу завершения и при этом выполняется утилита dumper, то формируется дамп образа памяти. Этот дамп можно просмотреть с помощью символьного отладчика. 3.3. Состояния процессов Процесс всегда находится в одном из следующих состояний: READY (готов) -_процесс может использовать центральный процессор (т.е. он не ждет наступления никакого события); BLOCKED (блокирован) -_процесс находится в одном из следующих состояний блокировки: SEND-блокирован; RECEIVE-блокирован; REPLY-блокирован; SIGNAL-блокирован; HELD (задержан) -_процесс получил сигнал SIGSTOP. До тех пор, пока он не выйдет из состояния HELD, ему не разрешается использовать центральный процессор. Вывести из состояния HELD можно либо выдачей сигнала SIGCONT, либо завершить процесс по сигналу; WAIT- (ожидает) -_процесс выдал wait() или waitpid() и ожидает информацию о состоянии порожденных им процессов; DEAD (мертв) -_процесс завершен, но не может передать информацию о своем состоянии породившему его процессу, поскольку тот не выдал функцию wait() или waitpid(). За завершенным процессом сохраняется состояние, но занимаемая им память освобождается. Процесс в состоянии DEAD также называют "зомби"-процессом. ВНИМАНИЕ. Более подробно состояния блокировок рассмотрены в разделе 2 "Микроядро". На рис. 15 представлены возможные состояния процесса в системе QNX. +--------+ +--------+ | HELD | | SEND +-----+ +--------+ | блокир.| |  |12 +--------+ | | | |  |2 | +-----------+ | | | 11 +------------+ 1 |6  +-------+ 9 +--------+ 3 +---------+ 7 +----------+ | WAIT |<-->| READY |<---| REPLY +<----| SIGNAL | |ожидает|10 +--------+5 |блокиров.+---->|блокирован| +-------+ | -----++---------+ 8 +----------+ +------++--------------- 4 14 13 +-----------+ +----------+ освобож- <----------| DEAD | | RECEIVE | дение про- | (зомби) | |блокирован| цесса +-----------+ +----------+ Определены следующие переходы из одного состояния в другое: 1. Процесс посылает сообщение. 2. Процесс-получатель принимает сообщение. 3. Процесс-получатель отвечает на сообщение. 4. Процесс ожидает сообщения. 5. Процесс принимает сообщение. 6. Сигнал разблокирует процесс. 7. Сигнал пытаетcя разблокировать процесс; получатель запрашивает сообщение о захвате сигнала. 8. Процесс-получатель принимает сигнал. 9. Процесс ожидает завершения порожденного процесса. 10. Порожденный процесс завершается, либо сигнал разблокирует процесс. 11. Процессу выдан SIGSTOP. 12. Процессу выдан SIGCONT. 13. Процесс завершается. 14. Порождающий процесс ожидает завершения, завершается сам или уже завершен. Рис.15 3.3.1. Определение состояний процессов Определить состояние конкретного процесса возможно: из интерпретатора (Shell) - с помощью утилит ps или sin; из программы - с помощью функции qnx_psinfo(). Определить состояние операционной системы в целом возможно: из интерпретатора - с помощью утилиты sin; из программы - с помощью функции qnx_osinfo(). Утилита ps определена стандартом POSIX, следовательно командные файлы с ее использованием являются мобильными. Утилита sin уникальна в QNX, она предоставляет полезную информацию о системе QNX, которую нельзя получить с помощью утилиты ps. 3.4. Символические имена процессов В QNX обеспечивается возможность разработки приложений, представляющих собой набор взаимодействующих процессов. Такие приложения отличаются более высокой степенью параллельной обработки данных, и кроме того, могут распределяться в сети, повышая этим производительность системы. Однако, разбиение приложения на взаимодействующие процессы требует специальных соглашений. Для того, чтобы взаимодействующие процессы могли надежно связываться друг с другом, они должны иметь возможность определять идентификаторы (ID) друг друга. Рассмотрим, например, сервер базы данных, который работает с произвольным количеством обслуживаемых процессов (клиентов). Клиенты могут обращаться к серверу в любое время, а сервер всегда должен быть доступен. Каким образом клиенты определяют идентификатор сервера базы данных для того, чтобы послать ему сообщение? В QNX эта проблема решается путем предоставления возможности присваивать процессам символические имена. В случае одного узла процессы могут зарегистрировать это имя с помощью Администратора процессов на том узле, где они выполняются. Другие процессы могут затем получить у Администратора процессов идентификатор процесса, соответствующий этому имени. В случае работы в сети проблема усложняется, т.к. сервер должен обслуживать клиентов, которые находятся на разных узлах сети. В QNX имеется возможность поддерживать работу как с глобальными, так и с локальными именами. Глобальные имена доступны во всей сети, а локальные - только на том узле, где они зарегистрированы. Глобальные имена начинаются со знака слэш (/). Например: qnx локальное имя; company/xyz локальное имя; /company/xyz глобальное имя. ВНИМАНИЕ. Рекомендуем начинать все имена с названия вашей фирмы, во избежание конфликтов с другими производителями программной продукции по поводу имен программ. Для того, чтобы использовать глобальные имена хотя бы на одном из узлов сети, необходимо запустить "определитель имен процессов" (утилита nameloc). Этот процесс содержит записи всех зарегистрированных глобальных имен. В одно и то же время в сети могут работать до десяти определителей имен процессов. Каждый имеет идентичную копию всех активных глобальных имен. Эта избыточность обеспечивает надежность работы сети, гарантируя работоспособность при одновременном аварийном завершении нескольких определителей имен процессов. Для регистрации имени процесс-сервер использует функцию Си qnx_name_attach(). Для определения имени процесса процесс-клиент использует функцию Си qnx_name_locate(). 3.5. Таймеры 3.5.1. Управление временем В QNX управление временем основано на использовании системного таймера. Этот таймер содержит текущее координатное универсальное время (UTC) относительно 0 часов 0 минут 0 секунд 1 января 1970 г. Для установки местного времени функции управления временем используют переменную среды TZ (описана в Руководстве пользователя). 3.5.2. Простые средства таймирования Программы интерпретатора Shell и процессы могут быть задержаны на заданное количество секунд с помощью простой утилиты таймирования. Программы интерпретатора используют для этого утилиту sleep; процессы - функцию Си sleep(). Можно также воспользоваться функцией delay(), в которой задается интервал времени в миллисекундах. 3.5.3. Более сложные средства таймирования Процесс может также создавать таймеры, задавать им временной интервал и удалять таймеры. Эти более сложные средства таймирования соответствуют стандарту POSIX 1003.4/Draft 9. 3.5.3.1. Создание таймеров Процесс может создать один или несколько таймеров. Таймеры могут быть любого поддерживаемого системой типа, а их количество ограничивается максимально допустимым количеством таймеров в системе (см. утилиту Proc в "Утилитах"). Для создания таймера используется функция Си mktimer(). Эта функция позволяет задавать следующие типы механизма ответа на события: -_перейти в режим ожидания до завершения. Процесс будет находиться в режиме ожидания начиная с момента установки таймера до истечения заданного интервала времени; -_оповестить с помощью proxy. Proxy используется для оповещения процесса об истечении времени ожидания; -_оповестить с помощью сигнала. Сформированный пользователем сигнал выдается процессу по истечении времени ожидания. 3.5.3.2. Установка таймеров Вы можете задать таймеру следующие временные интервалы: -_абсолютный. Время относительно 0 часов, 0 минут, 0 секунд, 1 января 1970 г.; _ относительный. Время относительно значения текущего времени. Можно также задать повторение таймера на заданном интервале. Например, вы установили таймер на 9 утра завтрашнего дня. Его можно установить так, чтобы он срабатывал затем каждые пять минут после истечения этого времени. Можно также установить новый временной интервал существующему таймеру. Результат этой операции зависит от типа заданного интервала: - для абсолютного таймера новый интервал замещает текущий интервал времени; - для относительного таймера новый интервал добавляется к оставшемуся временному интервалу. ----------------------------------------------------------------- Для установки Используйте ----------------------------------------------------------------- Абсолютного временного интервала Функцию abstimer() Относительного временного интервала Функцию reltimer() ----------------------------------------------------------------- 3.5.3.3. Удаление таймеров Для удаления таймера воспользуйтесь функцией Си rmtimer(). Таймер может удалить сам себя по истечении временного интервала при условии: - при вызове rmtimer() включена опция _TNOTIFY_SLEEP; - таймер неповторяемый. 3.5.3.4. Установка периода таймера Период таймера задается утилитой ticksize или функцией Си qnx_timerperiod(). Вы можете выбрать период в интервале от 500 микросекунд до 50 миллисекунд. 3.5.3.5. Считывание таймера Для определения оставшегося времени таймирования или для того, чтобы узнать, был ли таймер удален, используйте функцию Си gettimer(). 3.6. Обработчики прерываний Обработчики прерываний обслуживают прерывания аппаратной части компьютерной системы; реагируют на аппаратные прерывания и управляют на нижнем уровне передачей данных между компьютером и внешними устройствами. Физически обработчики прерываний формируются как часть стандартного процесса QNX (например, драйвера), но они всегда выполняются асинхронно с процессом, в котором содержатся. Обработчик прерываний: -_запускается удаленным вызовом, а не прямо прерыванием (лучше писать его на языке Си, а не на ассемблере); -_выполняется внутри процесса, в который встроен, поэтому имеет доступ ко всем глобальным переменным процесса; -_выполняется только для разрешенных прерываний и приоритетно обслуживает прерывания более высокого уровня; -_не взаимодействует непосредственно с контроллером прерываний (микросхемой 8259). Это делает операционная система. По одному прерыванию (если это поддерживается аппаратно) могут запускаться несколько процессов. При возникновении физического прерывания каждому обработчику прерываний передается управление. В каком порядке обработчики прерываний разделяют обработку этого прерывания - не определено. ----------------------------------------------------------------- Если вы хотите Используйте ----------------------------------------------------------------- Установить аппаратное прерывание Функцию qnx_hint_attach() Удалить аппаратное прерывание Функцию qnx_hint_detach() ----------------------------------------------------------------- 3.6.1. Обработчики прерываний от таймера Можно подключить обработчик прерываний напрямую к системному таймеру таким образом, чтобы обработчик запускался по каждому прерыванию от таймера. Для установки периода используйте утилиту ticksize. Можно также подключиться к масштабируемому прерыванию от таймера, которое выдается каждые 100 миллисекунд в зависимости от значения ticksize. Эти таймеры являются альтернативой таймерам стандарта POSIX 1003.4 при обработке прерываний нижнего уровня. 4. ПРОСТРАНСТВО ИМЕН СИСТЕМЫ ВВОДА/ВЫВОДА 4.1. Введение Ресурсы ввода/вывода не являются частью ядра. Обслуживающие программы ввода/вывода запускаются динамически при работе системы. Поскольку файловая система QNX является необязательным элементом, то пространство составных имен не является, как в большинстве монолитных операционных систем, частью файловой системы. 4.1.1. Префиксы и области полномочий В системе QNX пространство составных имен разделено на области полномочий. Любой процесс, выполняющий файл-ориетированное обслуживание ввода/вывода, регистрирует у Администратора процессов свой префикс, определяя часть пространства имен, с которым он собирается работать (т.е. область своих полномочий). Эти префиксы образуют дерево префиксов, которое хранится в памяти каждого компьютера, загруженного системой QNX. 4.2. Составные имена 4.2.1. Префиксы Администратора ввода/вывода При открытии файла, его составное имя сопоставляется с деревом префиксов для того, чтобы направить запрос open() к соответствующему администратору ресурсов ввода/вывода. Например, Администратор устройств (Dev) обычно регистрирует префикс /dev. Если процесс вызывает функцию open(), задавая /dev/xxx, то в результате совпадения начала составного имени с префиксом /dev, запрос open() будет направлен к администратору Dev (владельцу). Дерево префиксов может содержать частично перекрывающиеся области полномочий. В этом случае выбор осуществляется по принципу наибольшего совпадения. Например, предположим, что имеется три зарегистрированных префикса: / файловая система на диске (Fsys); /dev система символьных устройств (Dev); /dev/hd0 дисковый том (Fsys). Администратор файловой системы зарегистрировал два префикса - один для смонтированной файловой системы QNX (/), а один для блок-ориентированного специального файла, который представляет целиком физический жесткий диск (/dev/hd0). Администратор устройств зарегистрировал один префикс. Ниже в таблице приведен пример определения соответствующего администратора по принципу наибольшего совпадения. ---------------------------------------------------------------- Составные имена Совпадает Передается к ---------------------------------------------------------------- /dev/con1 /dev Dev /dev/hd0 /dev/hd0 Fsys /usr/dtdodge/test / Fsys ---------------------------------------------------------------- Дерево префиксов представляет собой список префиксов, разделенных двоеточиями, как показано ниже prefix=pid,unit:prefix=pid,unit:prefix=pid,unit pid -_это идентификатор процесса для администратора ресурсов ввода/вывода; unit -_это однознаковый номер, позволяющий выбирать один из нескольких возможных префиксов, которые имеет администратор. В предыдущем примере, если Fsys - это процесс 3, а Dev - процесс 5, то дерево системных префиксов могло бы выглядеть так /dev/hd0=3, a:/dev=5, а:/=3,е ----------------------------------------------------------------- Если вам нужно Воспользуйтесь ----------------------------------------------------------------- Отобразить на экране дерево префиксов Утилитой prefix Получить доступ к дереву префиксов Функцией qnx_prefix_query() из Си-программы ----------------------------------------------------------------- 4.2.2. Сетевой корень В системе QNX имеется понятие суперкорня или сетевого корня, использование которого позволяет задавать составные имена, минуя дерево префиксов конкретного узла. Сетевой корень задается двумя слешами, за которыми следует номер узла. Используя сетевой корень, можно легко получить доступ к файлам и устройствам, которые не входят в пространство составных имен вашего узла. Например, в типичной сети QNX следующие пути определяют: /dev/ser1 - последовательный порт своего узла; //10/dev/ser1 - последовательный порт узла 10; //0/dev/ser1 - последовательный порт своего узла; //20/usr/dtdodge/test - файл на узле 20. Обратите внимание на то, что //0 всегда относится к своему узлу. 4.2.3. Сетевой корень по умолчанию Если программа запускается на чужом узле, то приходится выполнять разрешение путевых имен в контексте пространства путевых имен своего узла. Например, команда //5 ls / запускающая утилиту ls на узле 5, должна обрабатывать также, как ls / запускаемая на своем узле. В том и другом случае префикс "/" должен быть разрешен по дереву префиксов своего узла, а не узла 5. В противном случае, можно представить себе беспорядок, который мог бы возникнуть, если бы префикс "/" рассматривался "своим" как для узла 5, так и для своего узла: файлы выбирались бы одновременно из совершенно разных файловых систем. С целью выбора нужного дерева префиксов при разрешении составных имен, не начинающихся с одного слэша (/), имеется возможность связать каждый процесс со своим сетевым корнем, используемым по умолчанию. После разрешения такого составного имени, перед ним добавляется сетевой корень, используемый по умолчанию. Например, если процесс имеет по умолчанию сетевой корень //9, то составное имя /usr/home/luc будет разрешено, как //9/usr/home/luc что интерпретируется как: "разрешить составное имя /usr/home/luc по дереву префиксов узла 9". Вновь создаваемые процессы наследуют сетевой корень, используемый по умолчанию, причем его значение инициализируется на своем узле после старта системы. Например, вы работаете на узле 9, находясь в интерпретаторе, в котором по умолчанию установлен сетевой корень - узел 9 (очень типичный случай). Если бы вам потребовалось выдать команду ls / то команда унаследовала бы используемый по умолчанию сетевой корень //9, в результате чего получилось бы ls //9/ Аналогично, если бы вы ввели команду //5 ls / то вы бы запустили команду ls на узле 5, но она унаследовала бы сетевой корень, используемый по умолчанию (//9), поэтому в результате снова получилось бы ls //9/. И в том, и в другом случае составное имя выбиралось бы из одного и того же пространства составных имен. ----------------------------------------------------------------- Если вы хотите Используйте ----------------------------------------------------------------- Получить ваш текущий сетевой корень, Си функцию qnx_prefix_getroot() используемый по умолчанию Установить ваш сетевой корень, Си функцию qnx_prefix_setroot() используемый по умолчанию Запустить программу с новым сетевым утилиту on корнем, используемым по умолчанию ----------------------------------------------------------------- 4.2.3.1. Передача составных имен между процессами Процессы, даже выполняющиеся на одном узле, могут иметь разные сетевые корни, используемые по умолчанию. Например, один из процессов мог унаследовать сетевой корень, используемый по умолчанию, от породившего его процесса, который находился на другом узле сети, либо сетевой корень, используемый по умолчанию, мог быть явно изменен породившим процессом. При передаче составных имен от одного процесса к другому, имеющему другой сетевой корень (например, при передаче файла системе буферизованной печати Spooler), необходимо определить сетевой корень до того, как составное имя будет передано процессу-получателю. Это можно не делать только в том случае, если вы уверены, что процесс-отправитель и процесс-получатель имеют один и тот же сетевой корень, используемый по умолчанию (либо если составное имя уже имеет лидирующие символы "//node/"). 4.2.4. Альтернативные префиксы Мы рассмотрели префиксы с точки зрения установления их соответствия администраторам ресурсов ввода/вывода. Существует другая форма префиксов, так называемые альтернативные префиксы, которые обеспечивают простую строковую замену заданного префикса. Альтернативный префикс задается следующим образом префикс = строка-замена Например, предположим, что вы работаете на машине, которая не имеет своей файловой системы (поэтому нет процесса, работающего с префиксом "/"). Однако, существует файловая система на другом узле (скажем, на 10), с которой вы хотите работать, используя "/". Вы можете выполнить это, используя следующий альтернативный префикс /=//10/ В этом случае ведущий символ (/) будет заменяться префиксом //10/. Например, имя /usr/dtdodge/test будет заменено на //10/usr/dtdodge/test. Это новое имя будет сопоставляться с деревом префиксов узла 10, т.к. оно начинается с символов "//10". В результате запрос open() будет направлен Администратору файловой системы узла 10. Таким образом, альтернативное имя позволит обеспечить доступ к удаленной файловой системе, как к своей собственной. Нет необходимости запускать процесс локальной файловой системы для выполнения переадресации. Дерево префиксов на рабочей станции, не имеющей своего диска, может выглядеть следующим образом /dev = 5,а:/=//10/ В этом случае имена, начинающиеся с "/dev", будут адресованы своему Администратору устройств, а запросы с другими именами - к администраторам удаленной файловой системы. 4.2.4.1. Создание специальных имен устройств Можно также использовать альтернативные префиксы для создания специальных имен устройств. Например, если Spooler печати работает на узле 20, то можно изменить имя своего принтера на альтернативное следующим образом /dev/printer=//20/dev/spool Любой запрос на открытие /dev/printer будет перенаправлен по сети к реальному Spooler печати. Аналогично, если у вас нет своего накопителя на гибких магнитных дисках, то вы можете обратиться к дисководу на узле 20, используя следующий альтернативный префикс /dev/fd0=//20/dev/fd0 В обоих рассмотренных выше случаях альтернативную переадресацию можно не выполнять, а к удаленному ресурсу обращаться непосредственно //20/dev/spool или //20/dev/fd0 4.2.5. Относительные составные имена Совершенно не обязательно начинать имена с одного или двух слэшей. В этом случае путь считается относительным к текущему рабочему каталогу. QNX хранит текущий рабочий каталог в виде символьной строки. Относительные составные имена всегда преобразуются в полные сетевые имена посредством добавления строки, содержащей имя текущего рабочего каталога, к относительному имени. Обратите внимание на то, что при задании текущего рабочего каталога результат будет различный, если начинать ее имя с одного слэша, либо с имени сетевого корня. 4.2.6. Текущий рабочий каталог Если текущий рабочий каталог имеет два ведущих слэша (сетевой корень), говорят, что он определен и привязан к пространству путевых имен определенного узла. Если, наоборот, он имеет один ведущий слэш, то к нему добавляется сетевой корень, используемый по умолчанию. Например, команда cd //18/ иллюстрирует первую (определенную) форму и привязывает все относительные составные имена к узлу 18, независимо от того, какой сетевой корень используется по умолчанию. Соответственно, вводя cd dev, в результате получим //18/dev. Команда cd/ служит примером второй формы, в которой результирующее составное имя, получаемое из относительного, будет зависеть от сетевого корня, используемого по умолчанию. Например, если по умолчанию используется сетевой корень //9, то вводя команду cd dev, в результате получим //9/dev. На самом деле все это не так сложно, как может показаться. Обычно сетевые корни (//node/) не определены, и вы работаете внутри своего пространства имен (которое определяется вашим сетевым корнем, используемым по умолчанию). Большинство пользователей, регистрируясь в системе, не меняют сетевой корень, который устанавливается системой по умолчанию, т.е. работают в пространстве имен своего узла. 4.2.6.1. О команде cd В некоторых версиях системы UNIX команда cd (сменить каталог) изменяет заданное ей имя, если это имя содержит символические связи. В результате сетевое имя нового текущего рабочего каталога, которое можно получить по команде pwd, может отличаться от заданного в команде cd. В QNX команда cd не изменяет путевое имя за исключением сокращений "..". Например, команда cd /usr/home/luc/test/../doc установит текущий рабочий каталог /usr/home/luc/doc, даже если отдельные элементы составного имени были символическими связями. Более подробно о символических связях и сокращениях см. раздел 5 "Администратор файловой системы". ВНИМАНИЕ. Для того, чтобы получить полное сетевое имя, вы можете воспользоваться утилитой fullpath. 4.3. Пространство имен описателей файлов При открытии какого-либо ресурса ввода/вывода, происходит обращение к различным пространствам имен. Функция open() возвращает целочисленное значение, которое называется дескриптором файла (FD), в дальнейшем используемое для направления запросов на ввод/вывод к соответствующему администратору. (Отметим, что функция Sendfd(), обращающаяся к ядру, вызываемая из библиотеки подпрограмм, используется для направления запроса.) Пространство имен дескрипторов файлов, в отличие от пространства составных имен, исключительно локально для каждого процесса. Для идентификации управляющей структуры, связанной с предыдущим вызовом функции open(), администратор использует комбинацию PID (идентификатор процесса) и FD (дескриптор файла). Эта структура называется управляющим блоком открытия (open control block - OCB) и содержится в администраторе ввода/вывода. На рис._16 показано, как администратор ввода/вывода устанавливает соответствие между отдельными парами PID, FD и OCB. "Системное пространство" "Пространство администрато- FD ра ввода/вывода" --------------> |+--------------------------------+ +------+ || | | | | | | | | | | | | | |+--+--+--+--+--+--+--+--+--+--+--| +--->| | || | | | | | | | | | | | | | | ОСВ |+--+--+--+--+--+--+--+--+--+--+--| | | | PID| | | |.-+--+--+--+--+--+--+--+-----+ | | |+--+--+--+--+--+--+--+--+--+--+--| +------+ || | | | | | | | | | | | |+--+--+--+--+--+--+--+--+--+--+--| | | | | | | | | | | | | +------+ +--+--+--+--+--+--+--+--+--+--+--| | | | | | | | | | | | | | | | | +--+--+--+--+--+--+--+--+--+--+--| +---->| | ОСВ | | | | | | | | | | | | | | | +--+--+--+--+--+--+--+--+--+--+--| | | | | | | | | | | |.-+--+--+--+--------+ +------+ +--+--+--+--+--+--+--+--+--+--+--| | | |. | | | | | | | | | +--------------------------------+ "разреженный массив" Рис. 16 4.3.1. Управляющие блоки открытия Управляющий блок открытия (OCB) содержит текущую информацию об открываемом ресурсе. Например, файловая система сохраняет текущий указатель поиска в файле. Каждая функция open() создает новый ОСВ. Поэтому, если процесс открывает один и тот же файл дважды, то любые вызовы lseek(), использующие один FD, не будут влиять на указатель поиска для другого FD. То же самое происходит, когда разные процессы открывают один и тот же файл. На рис. 17 схематически изображены два процесса, один из которых открывает два раза, а другой - один раз тот же самый файл. Для каждого процесса создаются свои дескрипторы файлов. дескрипторы управляющие блоки файлов (FD) открытия (OCB) +----+ +---+ OCB процесс 1 | 0 |------->| +----------------------+ +----| +---+ | | 1 |------->+---+ | +----| | +---------------+ | | | +---+ | | +----| | | | | | | +----| | | | | | | +----+ +--------------+ | /tmp/file | дескрипторы +--------------+ файлов (FD) | +----+ +---+ | | 0 +------> | +--------------------+ +----| +---+ процесс 2 | | +----| | | +----| | | +----| | | +----+ Процесс 1 открывает файл /tmp/file два раза. Процесс 2 открывает тот же файл один раз. Рис. 17 Несколько дескрипторов файлов одного или более процессов могут ссылаться на один и тот же ОСВ. Это достигается двумя способами: - процесс может использовать функции Си dup(), dup2(), fcntl() для создания второго дескриптора файла, который ссылается на тот же ОСВ; - при создании нового процесса посредством функций fork(), spawn() или exec() все дескрипторы файлов по умолчанию наследуются новым процессом; эти наследуемые дескрипторы ссылаются на те же ОСВ, что и соответствующие дескрипторы породившего процесса. Когда несколько дескрипторов файлов ссылаются на один и тот же ОСВ, то любое изменение состояния ОСВ немедленно становится "видимым" всем процессам, имеющим дескрипторы файлов, связанные с данным ОСВ. Например, если некоторый процесс использует функцию lseek() для изменения положения указателя поиска, то чтение или запись происходит с новой позиции указателя, и при этом совершенно не важно, какой дескриптор файла используется. На рис. 18 показаны два процесса, один из которых открывает файл дважды, а затем с помощью функции dup() - третий раз. Затем процесс порождает другой процесс, который наследует все открытые файлы. управляющие блоки открытия (OCB) дескрипторы +------+ файлов (FD) |  +----+ | +---+ OCB порождаю- | 0 |---+--->| +--------------------+ щий про- +----| | +---+ | цесс | 1 +---+-------+ | +----| | | | | 2 +---+-----+ | | +----| | | | | | | | | | | +----| | | | | | | | | |  +----+ | | | +--------------+ |   | /tmp/file | | +----+ +--------------+ дескрипторы | | |  файлов (FD) | | +--------------------+ +----+ | +----+ | 0 +---+   +----| | | порожден- | 1 +--------+ | ный про- +----| | цесс | 2 +-----------+ +----| | | +----| | | +----+ Процесс дважды открывает файл, а затем получает еще один FD с помощью функции dup(). Порожденный процесс наследует все три дескриптора. Рис. 18 Можно запретить наследование дескрипторов файлов процессами, создаваемыми функциями spawn() или exec(), с помощью функции fcntl(), установив флаг FD_CLOEXEC. 5. АДМИНИСТРАТОР ФАЙЛОВОЙ СИСТЕМЫ 5.1. Введение Администратор файловой системы (Fsys) позволяет стандартным образом организовать хранение и получение доступа к данным дисковых подсистем. Администратор Fsys отвечает за обработку всех запросов на открытие, закрытие, чтение и запись файлов. 5.2. Что такое файл В системе QNX под файлом понимается объект, над которым может быть выполнена либо операция записи, либо операция чтения, либо обе эти операции. В QNX имеется шесть типов файлов, пять из которых поддерживает администратор Fsys: регулярные файлы -_содержат последовательность байтов, доступ к которым произволен, и которые не имеют заранее определенной внутренней структуры; каталоги -_содержат информацию, необходимую для определения местонахождения регулярных файлов; кроме того, содержат информацию о статусе и атрибутах каждого регулярного файла; символические связи -_содержат составное имя файла или каталога, к которым требуется обеспечить доступ, вместо файла символической связи. Эти файлы часто используются для обеспечения доступа разными путями к одному и тому же файлу; программные каналы (pipe) и -_служат каналами ввода/вывода между взаимодействующими простые очереди (FIFO) процессами; блок-ориентированные -_описывают устройства, такие как накопители на дисках, специальные файлы накопители на магнитной ленте, и разделы диска. Доступ к этим файлам организован таким образом, что технические характеристики устройств "скрыты" от использующих их приложений. Все эти типы файлов подробно рассматриваются в этом разделе. Шестой тип файла - (символьный специальный файл), относится к сфере управления Администратора устройств. 5.2.1. Метки даты и времени Fsys хранит для каждого файла четыре различных значения времени: - дата последнего доступа (чтения); - дата последней записи; - дата последней модификации; - дата создания (уникальна в QNX). 5.2.2. Доступ к файлу Доступ к регулярным файлам и каталогам регулируется битами режима, хранящимися в индексном дескрипторе файла (об индексных дескрипторах см. в подразделе 5.4 "Связи и индексные дескрипторы"). Этими битами разрешается чтение, запись и выполнение файла в зависимости от используемого идентификатора пользователя и группы. Существует три градации доступа: - только пользователю; - только группе; - другим пользователям. Процесс может работать с файлом, имеющим идентификаторы пользователя или группы, отличные от идентификатора создавшего его процесса. Этот механизм реализован функциями setuid (установить эффективный идентификатор пользователя) и setgid (установить эффективный идентификатор группы). 5.3. Регулярные файлы и каталоги 5.3.1. Регулярные файлы В системе QNX регулярные файлы рассматриваются как последовательности байтов с произвольным доступом, которые не имеют никакой заранее определенной внутренней структуры. Прикладные программы сами определяют структуру и содержимое любого конкретного регулярного файла. Совокупности регулярных файлов образуют файловые системы. Файловые системы поддерживаются Администратором файловой системы и реализованы в начале блок-ориентированных специальных файлов, которые определяют разделы диска (описываются в подразделе 5.9 "Исходные тома"). 5.3.2. Каталоги Каталог - это файл, содержащий элементы каталога. Каждый элемент каталога устанавливает соответствие имени файла с файлом. Имя файла - это символическое имя, которое позволяет идентифицировать файл и получить к нему доступ. Файл может быть идентифицирован более чем одним именем файла (см. секцию "Связи и индексные дескрипторы" и "Символические связи"). На рис. 19 показано, как осуществляется поиск файла /usr/bill/file2 в структуре каталога. Корневой каталог +---------+ | / | +---------+ +--------------| | +---------------------+ |  +----------+ +----------+ | bin | | usr | +----------+ +----------+ +---+---+ +---+---+ | | | +------+ | +------+ | | +-------+ | | | | +-+ | | | | +------+ +------+ +------+ +--------+ +-------+ +-------+ | rm | | ls | | cat | | dtdodge| | bill | | gbbell| +------+ +------+ +------+ +--------+ +-------+ +-------+ +--+--+ | | | +-------+ | | | | +------+ | | | +--------+ +-------+ +-------+ | file 1 | | file 2| | file 3| +--------+ +-------+ +-------+ Путь по структуре каталогов QNX к файлу usr/bill/file2 Рис. 19 5.3.2.1. Операции с каталогом Хотя во многом каталог похож на стандартный файл, Администратор файловой системы накладывает некоторые ограничения на операции с каталогом. В частности, вы не можете открыть каталог на запись, а также не можете создать новую связь для каталога с помощью функции Си link(). 5.3.2.2. Чтение элементов каталога Для чтения элементов каталога используется набор POSIX-функций языка Си, которые обеспечивают к ним мобильный доступ: opendir() readdir() rewinddir() closedir() Поскольку каталоги QNX являются просто файлами, содержащими "известную" информацию, то можно считывать элементы каталога, используя функции open() и read(). Однако, этот метод не является мобильным, т.к. формат элементов каталога различен в разных операционных системах. 5.3.3. Экстенты В системе QNX регулярные файлы и файлы каталогов хранятся как последовательность экстентов. Экстент - это непрерывная последовательность блоков на диске. 5.3.3.1. Где хранятся экстенты Файлы, имеющие только один экстент, хранят информацию об экстенте в элементе каталога. Если же для файла требуется более одного экстента, то информация о местонахождении экстента хранится в одном или нескольких блоках связанных экстентов. Каждый блок экстентов может содержать информацию о нахождении 60 экстентов. На рис._20 представлен файл, состоящий из нескольких последовательных областей на диске (экстентов). +----------------------------+ | | |  +--------+ | +--------+ | | | +--------------------+---. | блок |элемент | | | +--------| экстентов |каталога| | | ~ ~ | | | | ~ ~ | | | | | | +--------| | | +--------| | | | | | . | +--------| | | +---+----| | . | .--+---+ | | | | +--------+ | +---+----+ | | | +--+ | +--+ | | |    +--------------+ +--------------+ +--------------+ | | | | | | | | | | | | | | | | | | +--------------+ +--------------+ .....+--------------+ первый второй последний экстент экстент экстент Рис. 20 5.3.3.2. Увеличение размера файла Когда Администратору файловой системы требуется увеличить размер файла, последний экстент которого уже заполнен, то он сначала пытается увеличить последний экстент хотя бы на один блок. Если это не удается сделать, то для расширяемого файла заводится новый экстент. Для размещения нового экстента Администратор файловой системы действует по принципу "первый пригодный". Специальная таблица Администратора файловой системы содержит элементы для каждого блока, представленного в файле /.bitmap (этот файл описан в разделе "Ключевые компоненты раздела QNX"). Каждый из этих элементов определяет наибольший непрерывный свободный экстент в области, определяемой соответствующим блоком. Администратор файловой системы выбирает первый элемент данной таблицы, достаточный по размерам для нового экстента. 5.4. Связи и индексные дескрипторы В системе QNX к файлу данных можно обращаться, используя более одного имени файла. Каждое имя называется связью. (Фактически существует два вида связей: жесткие связи, которые мы будем называть просто "связями" и символические связи. Символические связи описаны в следующем разделе.) Для поддержки связей каждого файла имя файла отделяется от информации, описывающей файл. Информация, не относящаяся к имени файла, хранится в структуре, называемой "индексным дескриптором" (inode). Если файл имеет только одну связь (т.е. одно имя файла), информация индексного дескриптора (т.е. информация, не относящаяся к имени файла) хранится в элементе каталога данного файла. Если файл имеет более одной связи, индексный дескриптор хранится как запись в специальном файле с именем /.inodes. Обратите внимание на то, что связь файла можно создать только в том случае, если файл и связь находятся в одной и той же файловой системе. /bin +---------+ +-------------------------+ | ls +--------->| содержимое файла | +---------| +-------------------------+ | | /.inodes +---------| +---------+ | more +----+ | | +---------| | +---------| +-------------------+ | | +> | +------>| содержимое файла | +---------| +> +---------| +-------------------+ | less +----+ | | +---------| +---------+ | | +---------+ На один и тот же файл ссылаются две связи с именами "more" и "less". Рис. 21 Существует еще две ситуации, при которых файл может иметь вход в файл /.inodes: - если имя файла превышает 16 символов, то служебная информация хранится в файле /.inodes, оставляя в элементе каталога место для 48-символьного имени файла; - если файл имел более одной связи, и все связи, кроме одной, были удалены, то за файлом остается отдельный элемент в файле /.inodes. В противном случае было бы невозможно найти элемент каталога, указывающий на элемент inode. (Обратной ссылки из inode к элементу каталога не существует.) Эту ситуацию можно исправить с помощью утилиты chkfsys. ----------------------------------------------------------------- Если вы хотите Используйте ----------------------------------------------------------------- Создать связь из интерпретатора Shell Утилиту ln Создать связь из программы Функцию Си link() ----------------------------------------------------------------- 5.4.1. Удаление связей При создании файла счетчику его связей присваивается единичное значение. По мере добавления связей значение счетчика увеличивается, при удалении - уменьшается. Область файла не удаляется с диска до тех пор, пока значение счетчика связей не станет равным нулю, и все программы, использующие файл, не закроют его. Это позволяет продолжать использовать открытый файл, даже если у него удалены все связи. ----------------------------------------------------------------- Если вы хотите Используйте ----------------------------------------------------------------- Удалить связи из интерпретатора Shell Утилиту rm Удалить связь из программы Функции Си remove() или unlink() ----------------------------------------------------------------- 5.4.2. Связи каталога Для каталога нельзя создать жесткие связи. Однако, каталоги имеют две жестко определенные связи: . - "точка"; .. - "точка точка". Имя файла "точка" ссылается на предшествующий каталог, заданный в составном имени, а "точка точка" ссылается на предыдущий предшествующему каталогу. /usr/home/fred/./test --------> /usr/home/fred/test --- | предшествующий /usr/home/fred/../eric -------> /usr/home/eric --- --- | | предшествующий | | предыдущий предшестующему Если нет предшествующего каталога, то "точка" ссылается на текущий каталог. Точно также, элемент "точка точка" после символа "/" означает просто "/", т.к. вы не можете выйти за пределы пути. 5.5. Символические связи Символическая связь - это специальный файл, который содержит составное имя в качестве данных. Когда в запросе ввода/вывода, например, в функции open(), встречается имя символической связи, то связываемая часть составного имени заменяется на содержимое файла связи, в результате чего путь переопределяется. Символические связи представляют собой гибкое средство косвенного задания составного имени, они часто используются для задания нескольких путей к одному и тому же файлу. В отличие от жестких связей символические связи могут пересекать файловые системы и могут создавать связи для каталогов. В следующем примере каталоги //1/usr/fred и //2/usr/barney связаны, несмотря на то, что они находятся в разных файловых системах и даже на разных узлах (см. рисунок). Этого нельзя было бы сделать, используя жесткие связи //1/usr/fred --> //2/usr/barney Обратите внимание на то, что символическая связь и адресуемый каталог могут иметь разные имена. В большинстве случаев символические связи используются для соединения одного каталога с другим. Однако, можно использовать символические связи и для файлов, например //1/usr/eric/src/test.c --> //1/usr/src/dame.c На рис. 22 представлены символические связи между двумя узлами. узел 1 узел 2 / / | | +-----+ +-----+ | usr | | usr | +-----+ +-----+ | | +-------+-------+ +------------+ | | | | | +----+ +----+ +----+ +-------+ +-------+ |eric| |src | |fred|- - - + |barney | | sam | +----+ +----+ +----+ +-------+ +-------+ | | | | .<- - - -+ | | +----+ +------+ | |src | |game.c| | | | +----+ +------+ + - - - - - - - - -> . | +-----------------+ | | | | +------+ +--------+ +-------+ |test.c+- - - - - -+ |hello.c | |myfile | +------+ +--------+ +-------+ Рис. 22 ----------------------------------------------------------------- Если вы хотите Используйте ----------------------------------------------------------------- Создать символическую связь Утилиту ln (с опцией -s) Удалить символическую связь* Утилиту rm Узнать, является ли файл символической Утилиту ls связью * Помните, что удаление символической связи действует только на связь и не действует на объект назначения. ----------------------------------------------------------------- Несколько функций оперируют непосредственно с символическими связями. Для этих функций замена символического элемента составного имени объектом назначения не выполняется. К этим функциям относятся: unlink() (которая удаляет символическую связь), lstat() и readlink(). Поскольку символические связи могут указывать на каталоги, то некорректная конфигурация может привести к возникновению замкнутых связей каталогов. Для того, чтобы избежать зацикливания, система накладывает ограничения на количество итераций. Это предельное значение задается как {SYMLOOP_MAX} в файле . 5.6. Программные каналы и FIFO-файлы 5.6.1. Программные каналы Программный канал (pipe) - это неименованный файл, который служит каналом ввода/вывода между двумя или несколькими взаимодействующими процессами: один процесс выполняет запись в канал, другой - чтение из канала. Администратор файловой системы таким образом обеспечивает буферизацию данных. Размер буфера определяется как {PIPE_BUF} в файле . Канал удаляется, как только он закрывается с двух сторон. Обычно программные каналы используются при параллельном выполнении двух процессов с однонаправленной передачей данных от одного процесса к другому. (Если требуется двунаправленный обмен данными, то вместо канала следует использовать сообщения.) Типичное применение программного канала - это соединение выхода одной программы со входом другой. Такое соединение часто выполняется интерпретатором Shell. Например, ls | move направляет стандартный выход от утилиты ls через канал на стандартный вход утилиты more. ----------------------------------------------------------------- Если вы хотите Используйте ----------------------------------------------------------------- Создать канал из интерпретатора Shell символ канала ("|") Создать канал из программы функции Си pipe() или popen() ----------------------------------------------------------------- ВНИМАНИЕ. На рабочих станциях, не имеющих дисков, можно вместо Администратора файловой системы запускать Администратор программных каналов в том случае, если требуется использование только каналов. Администратор каналов оптимизирует канальный ввод/вывод и может обеспечить более эффективное использование каналов, чем Администратор файловой системы. 5.6.2. FIFO-файлы FIFO-файлы - это, по существу, те же каналы, за исключением того, что они представляют собой именованные файлы, хранящиеся в каталогах файловой системы. ----------------------------------------------------------------- Если вы хотите Используйте ----------------------------------------------------------------- Создать FIFO-файл из интерпретатора Shell Утилиту mkfifo Создать FIFO-файл из программы Функцию Си mkfifo() Удалить FIFO-файл из интерпретатора Shell Утилиту rm Удалить FIFO-файл из программы Функции Си remove() или unlink() ----------------------------------------------------------------- 5.7. Производительность Администратора файловой системы Администратор файловой системы имеет ряд средств, обеспечивающих высокопроизводительный доступ к диску: - элеваторный доступ; - кэш-буфер; - многопоточную обработку; - управляемый клиентом приоритет; - временные файлы; - электронные диски (ramdisk). 5.7.1. Элеваторный доступ Элеваторный доступ минимизирует общее время доступа, требуемое для записи данных на диск. Невыполненные запросы на запись упорядочиваются таким образом, что все они выполняются за один проход головок диска с низшего до высшего адреса дискового пространства. Кроме того, при элеваторном доступе обеспечивается мультисекторная запись там, где это возможно. 5.7.2. Кэш-буфер Кэш-буфер представляет собой "интеллектуальный" буфер между Администратором файловой системы и драйвером диска. Задача кэш-буфера - хранить блоки файловой системы для того, чтобы минимизировать количество обращений Администратора файловой системы к диску. По умолчанию размер кэш-буфера определяется как некоторый процент от размера всей системной памяти, причем это значение можно изменять опцией в Fsys. Операции чтения синхронны. Наоборот, операции записи обычно асинхронны. При поступлении данных в кэш-буфер Администратор файловой системы оповещает обслуживаемый процесс о том, что данные записаны. Затем выполняется запись данных на диск, обычно с запаздыванием менее двух секунд. Программа управления кэш-буфером обычно дает приоритет операциям чтения относительно операций записи. Это правило нарушается только в двух случаях: - кэш-буфер близок к заполнению (операциям записи дается такой же приоритет, как и операциям чтения до тех пор, пока не освободится достаточное количество блоков); - критичные для файловой системы блоки, такие как блоки битовой карты, блоки каталогов, блоки экстентов и блоки индексных дескрипторов, записываются на диск немедленно и синхронно (эти критичные блоки записываются в обход стандартного механизма записи, включая элеваторный доступ). Приложения могут изменять порядок записи конкретно для каждого файла. Например, приложение, работающее с базой данных может установить синхронный порядок записи в данный файл. Это обеспечит высокую степень целостности файла, учитывая возможные сбои аппаратуры или питания, которые, в противном случае, могли бы привести к разрушению базы данных. 5.7.3. Многопоточная обработка Администратор файловой системы является процессом, осуществляющим многопоточную обработку, т.е. он может одновременно выполнять несколько запросов на ввод/вывод. Это позволяет Администратору файловой системы наиболее полно выполнять параллельную обработку, поскольку он может одновременно: - обеспечивать параллельный доступ к нескольким устройствам; -_удовлетворять запросы на ввод/вывод из кэш-буфера в то время, когда по другим запросам ввода/вывода выполняется доступ к физическому диску. 5.7.4. Управляемый процессами приоритет Приоритет Администратора файловой системы может определяться приоритетом процесса, посылающего сообщение. Когда Администратор файловой системы принимает сообщение, то его приоритет устанавливается равным приоритету процесса, пославшего сообщение. Более подробную информацию см. в подразделе 2.7 "Планирование процессов". 5.7.5. Временные файлы В QNX имеется возможность открывать временные файлы, которые записываются, а затем считываются в течение короткого интервала времени. Для таких файлов Администратор файловой системы стремится сохранить блоки данных в кэш-буфере и записывает блоки на диск только в случае крайней необходимости. 5.7.6. Электронные диски Администратор файловой системы имеет возможность организации электронного диска, что позволяет использовать до 8 Мбайт памяти для имитации физического диска. Поскольку Администратор файловой системы использует высокоэффективную передачу сообщений, состоящих из нескольких частей, данные пересылаются с электронного диска непосредственно в буфера приложений. Администратор файловой системы в этом случае не использует кэш-буфер, поскольку электронный диск организован в памяти и не снабжен драйвером. (Информация о передаче сообщений, состоящих из нескольких частей, содержится в разделе 2 "Микроядро".) Благодаря отсутствию задержек, которые вносит аппаратура и использование кэш-буфера, электронные диски обеспечивают большую скорость выполнения операций чтения/записи по сравнению с жесткими дисками. 5.8. Живучесть файловой системы Организация файловой системы QNX обеспечивает высокую пропускную способность и высокую надежность. Это достигается несколькими путями. Основной объем данных проходит через кэш-буфер и записывается на диск только после небольшой задержки, причем критичные для файловой системы данные записываются немедленно. Все обновления каталогов, индексных дескрипторов, блоков экстентов и битовых карт сразу же попадают на диск во избежание разрушения файловой системы на диске (т.е. никогда не должно возникнуть ситуации внутренней противоречивости данных на диске). В некоторых случаях возникает необходимость обновления всех вышеназванных структур. Например, если при записи файла в каталог последний экстент каталога окажется заполненным, то размер каталога должен увеличиться. В этом случае порядок действий должен быть определен очень аккуратно для того, чтобы в случае глобального сбоя (например, при сбое по питанию), при частично завершенном действии, файловая система после перезагрузки осталась бы цела. В худшем случае, некоторые блоки могут быть отмечены как занятые, но не использованы. Их можно в дальнейшем освободить, запустив утилиту chkfsys. 5.8.1. Восстановление файловой системы Даже в самых хороших системах могут происходить следующие отказы: -_появление на диске сбойных блоков вследствие бросков по питанию; -_неопытный или злонамеренный пользователь, имея доступ к привелегиям суперпользователя может реинициировать файловую систему (посредством утилиты dinit); -_из-за ошибок программа (особенно работающая не в среде QNX) может проигнорировать информацию о разделении диска и перезаписать часть раздела QNX. Поэтому, для того, чтобы можно было восстановить как можно больше файлов в случае возникновения указанных отказов, на диск записываются уникальные "метки", которые помогают при автоматической идентификации и восстановлении критических частей файловой системы. Файл индексных дескрипторов (/.inodes), а также каждый каталог и блок экстентов содержат уникальные структуры данных, которые утилита chkfsys использует для восстановления поврежденной файловой системы. Более подробно о восстановлении файловой системы смотрите в описании утилиты chkfsys. 5.9. Исходные тома Администратор файловой системы управляет блок-ориентированными специальными файлами. Эти файлы определяют диски и разделы дисков. 5.9.1. Диски и дисковые подсистемы В системе QNX каждый физический диск компьютера представлен блок-ориентированным специальным файлом. С точки зрения файловой системы QNX диск рассматривается как последовательный набор блоков, длиною по 512 байт каждый, независимо от объема диска. Блоки нумеруются, начиная с первого блока диска (блок1). Поскольку каждый диск - это блок-ориентированный специальный файл, то он может быть открыт для доступа на физическом уровне, с использованием функций Си стандарта POSIX, таких, как open(), close(), read() и write(). На уровне блок-ориентированного специального файла, который определяет весь диск, система QNX не накладывает никаких ограничений на структуры данных, которые могут существовать на диске. На компьютере, загруженном системой QNX, может быть одна или несколько дисковых подсистем. Каждая дисковая подсистема состоит из контроллера и одного или нескольких дисков. Для каждой дисковой подсистемы, которая должна управляться Администратором файловой системы, запускается процесс драйвера устройств. 5.9.2. Разделы операционной системы Система QNX отвечает требованиям фактически действующего промышленного стандарта, который допускает разделение одного и того же физического диска несколькими операционными системами. В соответствии с этим стандартом в таблице разделов можно определить до четырех первичных разделов на диске. Таблица хранится в первом блоке диска. Каждому разделу должен быть задан "тип", опознаваемый операционной системой, которая собирается работать с данным разделом. В таблице, приведенной ниже, указаны типы разделов операционной системы, используемые в настоящее время: ----------------------------------------------------------------- Тип Операционная система ----------------------------------------------------------------- 1 DOS (12-битовая FAT) 4 DOS (16-битовая FAT) 5 Раздел расширения DOS 6 Большие разделы DOS 4.0 (>32 Мбайт) 7 QNX 1.x и 2.x ("qnx") 7 OS/2 HPFS 8 QNX 1.x и 2.x ("qny") 9 QNX 1.x и 2.x ("qnz") 77 QNX 4.x 78 QNX 4.x 79 QNX 4.x 99 UNIX ----------------------------------------------------------------- Если вам требуется более одного раздела для QNX 4.x на одном физическом диске, то следует использовать тип 77 для первого раздела QNX, тип 78 для второго раздела QNX и тип 79 для третьего раздела QNX. В принципе можно использовать другие типы для второго и третьего разделов, однако 78 и 79 предпочтительнее. Для того, чтобы отметить любой из этих разделов как загружаемый, следует воспользоваться утилитой fdisk. Во время загрузки загрузчик QNX (инсталлируемый утилитой fdisk) позволяет изменить номер загружаемого раздела, который задан по умолчанию в таблице разделов. Утилиту fdisk можно использовать для создания, модификации и удаления разделов. Поскольку в системе QNX каждый раздел диска рассматривается как блок-ориентированный специальный файл, то доступ можно получить: - либо ко всему диску, независимо от разделов, как к блок-ориентированному специальному файлу; -_к одному разделу, как к блок-ориентированному специальному файлу. Этот файл будет входить в состав блок-ориентированного специального файла, описывающего весь диск. Таблица разделов | | +-----------+ +------------+ | | -- +-----------+ -- -- ------------- -- | +--+ | | | +--+ | | | | DOS |/dev/hd0t4 | | DOS |/dev/hd1t4 | +-----------| -- | +-----------+ -- | | | | /dev/hd1| | | | | | | | | | /dev/hd0 | QNX |/dev/hd0t77 | | | | | | | | | | QNX |/dev/hd1t77 | | | | | | | | | +-----------| +- | | | | | | |/dev/hd0t99 | | | | | | UNIX | | | | | | -- +-----------+ -- -- ------------- -- жесткий диск 1 жесткий диск 2 Два физических диска. Первый диск содержит разделы DOS, QNX и UNIX. Второй - разделы DOS и QNX. Рис. 23 5.9.3. Определение блок-ориентированных специальных файлов Имена всех блок-ориентированных специальных файлов содержатся в дереве префиксов того компьютера, в дисковой памяти которого эти файлы размещены (дерево префиксов описано в разделе 3 "Пространство имен ввода/вывода"). Когда запускается драйвер устройств для дисковой подсистемы, Администратор файловой системы автоматически регистрирует префиксы, которые определяют блок-ориентированные специальные файлы для каждого физического дисковода конкретной дисковой подсистемы. Допустим, что у вас имеется контроллер диска Western Digital, к которому подключены два дисковода. На одном вы хотите смонтировать раздел DOS, раздел QNX и раздел UNIX. На другом дисководе - раздел DOS и раздел QNX. Администратор файловой системы определит блок-ориентированные специальные файлы /dev/hd0 и /dev/hd1 для этих двух дисководов на контроллере, где запущен драйвер. Затем вы можете воспользоваться утилитой mount для определения блок-ориентированных специальных файлов для каждого раздела. Например, команда mount -p /dev/hd0 -p /dev/hd1 создаст следующие блок-ориентированные специальные файлы. ------------------------------------------------------------------- Раздел ОС Блок-ориентированный специальный файл ------------------------------------------------------------------- Раздел DOS на диске hd0 /dev/hd0t4 Раздел QNX на диске hd0 /dev/hd0t77 Раздел UNIX на диске hd0 /dev/hd0t99 Раздел DOS на диске hd1 /dev/hd1t4 Раздел QNX на диске hd1 /dev/hd1t77 ------------------------------------------------------------------- Обратите внимание на то, что сочетание tn указывает на раздел диска, используемый конкретной операционной системой. Например, раздел DOS - это t4, раздел UNIX - t99 и т.д. 5.9.4. Монтирование файловой системы Обычно файловая система QNX монтируется как блок-ориентированный специальный файл. Для монтирования файловой системы используется утилита mount, которая задает префикс, идентифицирующий файловую систему. Например, команда mount /dev/hd0t77 / монтирует файловую систему с префиксом "/" на разделе, определенном блок-ориентированным специальным файлом с именем hd0t77. ВНИМАНИЕ. Если диск разбит на разделы, то необходимо смонтировать блок-ориентированный специальный файл раздела (например, /dev/hd0t77), который определяет раздел QNX 4.x, а не основной блок-ориентированный специальный файл, который определяет весь физический диск (например, /dev/hd0). Если вы попытаетесь смонтировать основной блок-ориентированный специальный файл для всего диска, то при попытке доступа к файловой системе получите сообщение "corrupt filesystem" ("испорченная файловая система"). 5.9.5. Демонтирование файловой системы Для демонтирования файловой системы используется утилита umount. Например, следующая команда демонтирует файловую систему вашего исходного раздела QNX umount /dev/hd0t77 После демонтирования файловой системы доступ к файлам ее раздела становится невозможным. 5.10. Ключевые компоненты раздела QNX Несколько ключевых компонентов, находящихся в начале каждого раздела QNX, составляют основу файловой системы: - блок загрузчика; - корневой блок; - битовая карта; - корневой каталог. Эти компоненты создаются при инициализации файловой системы по команде dinit. На рис. 24 представлена структура файловой системы QNX в разделе диска. +------------------------ | загрузчик |  +--------------------| | | корневой блок | | +--------------------| | | | | +-- битовая --| | | карта | | +-- --| | | | | Раздел QNX +--------------------| | | | | +-- --| | | корневой | | +-- --| | | каталог | | +-- --| | | | | +--------------------| | | | | +-- --| | | . | | +-- --| | | . | | +-- --| | | . | | +-- --| | | |  +------------------------ Рис. 24 5.10.1. Блок загрузчика Блок загрузчика_-_это первый физический блок раздела диска. Этот блок содержит программный код, который загружается и затем выполняется базовой системой ввода/вывода (BIOS) компьютера, при загрузке операционной системы из раздела. Если диск не разбит на разделы (например, в случае гибкого диска), то этот блок является первым физическим блоком диска. 5.10.2. Корневой блок Корневой блок имеет структуру стандартного каталога. Он содержит служебную информацию для следующих четырех специальных файлов: - корневой каталог файловой системы (/); - /.inodes - /.boot - /.altboot Файлы /.boot и /.altboot содержат образы операционных систем, которые могут быть загружены программой начальной загрузки QNX. Обычно программа начальной загрузки QNX загружает образ ОС, хранящийся в файле /.boot. Но в случае непустого файла /.altboot имеется возможность загрузить образ ОС, хранящийся в файле /.altboot. 5.10.3. Битовая карта Для распределения дискового пространства в системе QNX используется битовая карта, хранящаяся в файле /.bitmap. Этот файл служит отображением всех блоков диска с указанием, какие блоки используются. Каждый блок представлен одним битом. Единичное значение бита указывает на использование соответствующего ему блока в данный момент времени. 5.10.4. Корневой каталог Корневой каталог раздела представляет собой обычный каталог, за исключением следующего: -_как "точка", так и "точка точка" являются связями к одной и той же служебной информации, а именно - к служебной информации корневого каталога корневого блока; -_корневой каталог всегда имеет элементы для файлов /.bitmap, /.inodes, /.boot и /.altboot. Программы, информирующие об использовании файловой системы, воспринимают эти элементы, как обычные файлы. 5.11. Администратор файловой системы DOS В системе QNX работа с пространством имен ввода/вывода организована посредством префиксов, которые адресуют запросы на доступ к файлам к соответствующему процессу-администратору. Это использует Администратор файловой системы DOS (Dosfsys). Dosfsys работает с префиксом /dos и представляет набор файлов DOS в пространстве имен системы QNX как "гостевую" файловую систему. Dosfsys обеспечивает прозрачный доступ к дискам DOS таким образом, что с файловыми системами DOS можно работать также, как с файловыми системами QNX. Эта прозрачность позволяет процессам работать с файлами DOS непосредственно. Стандартные библиотечные функции ввода/вывода, такие, как open(), close(), read() и write() работают идентично как с файлами раздела DOS, так и с файлами раздела QNX. Например, для того, чтобы скопировать файл из раздела QNX в раздел DOS, достаточно ввести команду cp /usr/luc/file.dat /dos/c/file.dat ----- | составное имя диска DOS С:, | работающего под QNX | Команда cp не содержит никаких специальных признаков, позволяющих определить, находится ли копируемый файл в разделе DOS. Другие команды также работают в прозрачной среде (например: cd, ls, mkdir). В том случае, если не существует эквивалента функции QNX в среде DOS, например, mkfifo() или link(), то Dosfsys возвращает соответствующий код ошибки (errno). Dosfsys работает как с гибкими дисками, так и с разделами жесткого диска. Все операции доступа к диску на нижнем уровне, требуемые администратору Dosfsys, выполняются с использованием стандартных функций, поддерживаемых Администратором файловой системы. Таким образом, Dosfsys, не используя программного кода низкого уровня, обеспечивает интерфейс между приложениями, работающими под управлением QNX и файловой системой DOS. ВНИМАНИЕ. Для выполнения программ DOS в среде QNX существует специальный пакет Rundos.  6. АДМИНИСТРАТОР УСТРОЙСТВ 6.1. Введение Администратор устройств (Dev) обеспечивает интерфейс между процессами и периферийными устройствами. Имена периферийных устройств принадлежат пространству имен ввода/вывода и начинаются с префикса /dev. Например, консоль в системе QNX имеет имя /dev/con1. 6.2. Работа с устройствами Программы QNX получают доступ к периферийным устройствам с помощью стандартных функций read(), write(), open() и close(). Для процесса QNX периферийное устройство представляется двунаправленным потоком байтов, который может считываться или записываться процессом. Администратор устройств регулирует прохождение потока между приложением и устройством. Частичная обработка этих данных выполняется администратором Dev в соответствии с параметрами, заданными в структуре управления периферийными устройствами (termios), которая существует для каждого устройства. Пользователи могут просмотреть и/или изменить эти параметры, используя утилиту stty; в программе для этой цели используются функции tcgetattr() и tcsetattr(). Параметры tеrmios управляют функционированием устройств на низшем уровне, в частности они задают: - алгоритм передачи данных (включая скорость передачи, четность, стоп-биты и биты данных); - отображение на экране дисплея вводимых с клавиатуры символов; - редактирование вводимой строки; - распознавание, активизацию и зависания; - программное и аппаратное управление потоком данных; - трансляцию выводимых символов. Кроме того, Администратор устройств реализует ряд дополнительных функций, с помощью которых процессы могут управлять периферийными устройствами. В приведенной ниже таблице представлены некоторые из этих функций. ----------------------------------------------------------------- Процесс может Посредством функции Си ----------------------------------------------------------------- Выполнять синхронизированные операции чтения dev_read() или read() + tcsetattr() Асинхронно оповестить процесс о доступности dev_arm() данных на одном или нескольких устройств ввода Ожидать полного завершения передачи выходных tcdrain() данных Послать прерывание по каналу связи tcsendbreak() Отсоединить коммуникационный канал tcdropline() Ввести данные dev_insert_chars() Выполнить неблокирующее чтение или запись open() и fcntl() (режим O_NONBLOCK) ----------------------------------------------------------------- 6.3. Режим редактируемого ввода Наиболее важным режимом работы с устройствами управляет бит ICANON управляющей структуры termios. Если этот управляющий бит установлен, то Администратор устройств выполняет функции строчного редактирования принимаемых символов. Таким образом, данные будут доступны для обработки прикладным процессам только при вводе строки, что обычно определяется поступлением кода символа "возврат каретки" (CR). Этот режим работы называется режимом редактируемого ввода, каноническим или, иногда, "cooked". Большинство неполноэкранных приложений работают в режиме редактируемого ввода. Интерпретатор Shell является типичным примером. В следующей таблице представлены некоторые специальные управляющие символы, которые могут быть заданы в управляющей структуре termios для того, чтобы указать Администратору Dev, каким образом выполнять редактирование. ----------------------------------------------------------------- Dev будет выполнять При получении ----------------------------------------------------------------- Перемещение курсора на один символ влево LEFT Перемещение курсора на один символ вправо RIGHT Перемещение курсора в начало строки HOME Перемещение курсора в конец строки END Стирание символа слева от курсора ERASE Удаление символа в текущей позиции курсора DEL Стирание всей вводимой строки KILL Стирание текущей строки и переход к предыдущей строке UP Стирание текущей строки и переход к следующей строке DOWN Переключение между режимами вставки и наложения INS (каждая новая строка начинает вводиться в режиме наложения) ----------------------------------------------------------------- Символы, управляющие редактированием строки, различаются для разных терминалов. Консоль QNX всегда работает с полным набором определенных клавиш редактирования. Если терминал подключен к компьютеру, на котором загружена система QNX, через последовательный порт, то необходимо определить управляющие символы редактирования для данного конкретного терминала. Для этого используется утилита stty. Например, если вы подключили терминал VT100 через последовательный порт (именуемый /dev/ser1), то для того, чтобы извлечь соответствующие коды клавиш редактирования и передать их к /dev/ser1, можно использовать следующую команду stty term=vt100 |Буфер | +----------------+ |виртуаль-|< -+ |ного ка- | | | 2 |нала В | | + -- -- -- -- - --+ +---------+ |  | +------+ +------+ +------+ | | .---+--->| .---+--->| | | +-------------+ +------| +------| +------| | |Сетевой |<- -- -| | | | |SRC ID| | |администратор| 3 | | | | +------| | +-------------+ | | | | |DST ID| | | | | | | +------| | | | | | | .---+-----------+ | 4 +------+ +------+ +------+ Рабочие очереди |  +-------------------+ | Сетевая среда | +-------------------+ Процесс выдает Send() или Reply() удаленному узлу Рис. 26 В случае выдачи Send() или Reply() на удаленный узел, происходят следующие действия: 1. Процесс вызывает функции Send() или Reply(), после чего ядро копирует данные из адресного пространства процесса в буфер соответствующего виртуального канала (ВК), определенного функцией Send() или Reply(); 2. Ядро ставит заявку в очередь к Сетевому администратору в порядке времени ее поступления, указывая в ней отправителя, удаленного получателя и указатели на данные в буфере виртуального канала. Если до этого очередь была пуста, то запускается proxy Cетевого администратора, оповещая его о том, что появилась работа; 3. Сетевой администратор принимает заявку из очереди; 4. Сетевой администратор начинает передачу по сети. Он отвечает за доставку. В случае передачи сигнала или создании ВК заявку в очередь ставит не ядро, а Администратор процессов. При этом Сетевой администратор обеспечивает передачу информации по назначению таким же образом. На рис. 27 представлены потоки данных и управляющих воздействий при приеме сообщений от удаленного узла. +--------------+ +--------------+ | Процесс В | | Микроядро +- -+ +--------------+ +--------------+   |4 | | | |3 | | | +- -- -- -- - -+ +--------------+ +-------+ | Сетевой | 2 | | | администратор+ -- -- -- - -- --> |Буфер | +--------------+ |ВК А |  | | |1 | | +-------+ | +-------------------------+ | Сетевая среда | +-------------------------+ Процесс принимает Send() или Reply() от удаленного узла. Рис. 27 Если удаленный узел послал сообщение так, как это было описано выше, то на узле, принимающем сообщение, будет происходить следующее: 1. По сети поступают данные; 2. Сетевой администратор копирует данные сообщения в соответствующий буфер виртуального канала; 3. Сетевой администратор оповещает ядро о завершении приема; 4. Ядро копирует данные из буфера виртуального канала в буфер процесса (предполагая, что он был RECEIVE- или REPLY-блокирован). Любые управляющие сообщения, принимаемые Сетевым администратором, немедленно передаются Администратору процессов посредством стандартной функции Send(). Эти управляющие сообщения используются для передачи сигналов и создания ВК. 7.4. Сетевые драйверы Подобно Администратору файловой системы и Администратору устройств, в состав Сетевого администратора не входят программы, управляющие работой конкретной аппаратуры. Эти функции реализуются драйверами сетевых карт. Сетевой администратор может поддерживать одновременно несколько сетевых драйверов. Обычно каждый драйвер поддерживает одну сетевую карту. Драйверы и карты могут быть одного или разных типов, например, два драйвера и две карты типа Ethernet или драйвер и карта типа Ethernet и драйвер и карта типа Arcnet. Интерфейс между Сетевым администратором и драйверами реализуется посредством очередей в совместно используемой памяти. Этот интерфейс обеспечивает максимально возможную производительность. Драйверы реализуют соответствующий протокол сетевой среды. Драйвер отвечает за упаковку данных, задание последовательности их передачи и повторную передачу, в случае запроса на передачу данных к удаленному физическому узлу с гарантированной надежностью. По умолчанию это относится ко всем примитивам передачи сообщений QNX. Такое решение позволяет легко адаптировать систему QNX к новому сетевому оборудованию и новым сетевым протоколам путем замены или модификации сетевого драйвера. 7.5. Идентификаторы узлов и сетей Каждый узел в локальной вычислительной сети идентифицируется двумя номерами: его физическим идентификатором (ID) узла и логическим идентификатором узла. 7.5.1. Физические идентификаторы узлов Физические ID узлов устанавливаются аппаратно. Сетевые карты взаимодействуют друг с другом, задавая физические ID тех удаленных узлов, с которыми им требуется установить связь. В случае сетей Ethernet и Token Ring этот идентификатор представляет собой большое число, с которым неудобно работать как оператору, так и утилитам. Например, каждая карта Ethernet и Token Ring имеет 48-битовый физический ID узла, соответствующий стандарту IEEE 802. Карты Arcnet имеют 8-битовые ID. Физические ID узлов имеют существенный недостаток: при взаимодействии нескольких сетей адреса могут конфликтовать друг с другом (особенно в случае Arcnet) или иметь совершенно другой формат. 7.5.2. Логические идентификаторы узлов Для разрешения указанной выше проблемы с физическими ID узлов каждому узлу QNX присваивается логический ID узла. Все процессы QNX работают с логическими ID узлов. Физические ID узлов скрыты для процессов, работающих в системе QNX. Логические ID узлов упрощают лицензирование сетей и приложений. Кроме того, для отдельных утилит, выполняющих опрос узлов сети, эта процедура упрощается, благодаря использованию простого цикла, в котором логический ID узла изменяется от 1 до значения общего количества узлов. Соответствие между логическими и физическими ID узлов устанавливается Сетевым администратором. Драйвер получает физический ID от Сетевого администратора при запуске его для передачи данных на другой узел. Логические ID узлов обычно принимают последовательные значения чисел, начиная с 1. Например, узлу, имеющему карту Ethernet, может быть присвоен логический ID узла, равный 2, который соответствует физическому ID узла, имеющему значение 00:00:C0:46:93:30. Логические ID узлов должны быть одинаковыми во всех взаимосвязанных сетях QNX. 7.5.3. Идентификаторы логических сетей Сетевой ID идентифицирует конкретную логическую сеть. Под логической сетью понимается любое аппаратное обеспечение, позволяющее сетевому драйверу напрямую установить связь с сетевым драйвером другого узла. В простейшем случае это может быть последовательный порт, а в самом сложном - сеть Ethernet с мостами . На рис. 28 показано, что узел 7 имеет две сетевые карты, которые позволяют ему получить доступ к узлам логических сетей 1 и 2. Узлы 8 и 9 имеют по три сетевые карты, связывающие их с сетями 1, 2 и 3. Обратите внимание на то, что каждый логический ID узла не один и тот же для всех трех логических сетей. ВНИМАНИЕ. Идентификаторы логических сетей и узлов присваиваются администратором системы. Более подробно об этом см. "Network Installation" в User's Guide. ---.--------------.--------------.----------------- Логическая | | | сеть 1 ---+---.----------+---.----------+---.------------- Логическая | | | | | | сеть 2 ---+---+----------+---+---.------+---+---.--------- Логическая +-----------+ +-----------+ +------------+ сеть 3 || | | | | || | | | | || || | | | | | | || | | | | || | | | | || || | | | | | | |+-+ +-+ | |+-+ +-+ +-+| |+-+ +-+ +-+ | | | | | | | | Логический| | Логический| | Логический | | узел 7 | | узел 8 | | узел 9 | | | | | | | +-----------+ +-----------+ +------------+ Несколько физических сетей успешно работают вместе как логические сети. Рис. 28 7.6. Выбор сети В том случае, когда узлы объединены более чем одной логической сетью, Сетевому администратору приходится выбирать, какую сеть использовать. Например, на приведенном выше рисунке узел 7 может передать данные узлу 8, используя драйвер, подключенный к сети 1 или к сети 2. 7.6.1. Балансировка нагрузки Пропускная способность сети определяется как бысродействием используемых в ней компьютеров, так и скоростью передачи данных, которую может обеспечить сетевое оборудование. Если компьютер выдает данные в сеть быстрее, чем сеть может принять их, то в этом случае общую пропускную способность сети ограничивает используемое сетевое оборудование. Например, два 486-х компьютера, соединенные по сети Arcnet будут ограничены предельной для этой сети скоростью передачи 2,5 Мбит/сек. Если поставить по две сетевые карты Arcnet в каждый компьютер и соединить их отдельными кабелями, то Сетевой администратор сможет передавать данные по двум сетям одновременно. При высокой нагрузке это позволит обеспечить производительность в два раза большую, чем производительность одной сети. Сетевой администратор будет пытаться сбалансировать нагрузку, выбирая для этого соответствующий сетевой драйвер. Если в приведенном выше примере при передаче данных от узла 7 к узлу 8 по сети 1 инициируется еще одна передача от узла 7 к узлу 8, то для нее будет автоматически выбрана сеть 2. 7.6.2. Отказоустойчивость В том случае, когда узлы объединены двумя или более сетями, существует несколько путей для установления связи между узлами. В случае выхода из строя одной из сетевых карт, в результате которого передача данных по этой сети становится невозможной, Сетевой администратор автоматически перенаправляет весь поток данных по другой сети. Это выполняется "на лету", без вмешательства прикладного программного обеспечения, обеспечивая системе прозрачную сетевую отказоустойчивость. Кроме того, при использовании нескольких сетей с независимым кабельным соединением, система в целом будет защищена от случайного обрыва кабеля. Можно создать систему-тандем, в которой две машины соединены высокоскоростной сетью, используемой для нормальной работы, и более дешевой низкоскоростной сетью (например, последовательным соединением), которая остается в резерве. В случае выхода из строя первой сети передача данных не прервется, хотя пропускная способность системы, естественно, понизится.