Операционная система QNX 4.Архитектура системы

         

Символические имена процессов


В QNX обеспечивается возможность разработки приложений, представляющих собой набор взаимодействующих процессов. Такие приложения отличаются более высокой степенью параллельной обработки данных, и кроме того, могут распределяться в сети, повышая этим производительность системы.

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

В QNX эта проблема решается путем предоставления возможности присваивать процессам символические имена. В случае одного узла процессы могут зарегистрировать это имя с помощью Администратора процессов на том узле, где они выполняются. Другие процессы могут затем получить у Администратора процессов идентификатор процесса, соответствующий этому имени.

В случае работы в сети проблема усложняется, т.к. сервер должен обслуживать клиентов, которые находятся на разных узлах сети. В QNX имеется возможность поддерживать работу как с глобальными, так и с локальными именами. Глобальные имена доступны во всей сети, а локальные - только на том узле, где они зарегистрированы. Глобальные имена начинаются со знака слэш (/). Например:

qnx - локальное имя;

    company/xyz - локальное имя;

    /company/xyz - глобальное имя.

Рекомендуем начинать все имена с названия вашей фирмы, во избежание конфликтов с другими производителями программной продукции по поводу имен программ.

Для того, чтобы использовать глобальные имена хотя бы на одном из узлов сети, необходимо запустить "определитель имен процессов" (утилита nameloc). Этот процесс содержит записи всех зарегистрированных глобальных имен.

В одно и то же время в сети могут работать до десяти определителей имен процессов. Каждый имеет идентичную копию всех активных глобальных имен. Эта избыточность обеспечивает надежность работы сети, гарантируя работоспособность при одновременном аварийном завершении нескольких определителей имен процессов.

Для регистрации имени процесс-сервер использует функцию Си qnx_name_attach(). Для определения имени процесса процесс-клиент использует функцию Си qnx_name_locate().



Символические связи


Символическая связь - это специальный файл, который содержит составное имя в качестве данных. Когда в запросе ввода/вывода, например, в функции 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 представлены символические связи между двумя узлами.

Рис. 22



Если вы хотите Используйте
Создать символическую связь Утилиту ln (с опцией -s)
Удалить символическую связь* Утилиту rm
Узнать, является ли файл символической связью Утилиту ls
* Помните, что удаление символической связи действует только на связь и не действует на объект назначения.

Несколько функций оперируют непосредственно с символическими связями. Для этих функций замена символического элемента составного имени объектом назначения не выполняется. К этим функциям относятся: unlink() (которая удаляет символическую связь), lstat() и readlink().

Поскольку символические связи могут указывать на каталоги, то некорректная конфигурация может привести к возникновению замкнутых связей каталогов. Для того, чтобы избежать зацикливания, система накладывает ограничения на количество итераций. Это предельное значение задается как {SYMLOOP_MAX} в файле .



Синхронизация процессов


Передача сообщений служит не только для обмена данными между процессами, но, кроме того, является средством синхронизации выполнения нескольких взаимодействующих процессов.

Давайте обратимся снова к приведенному выше рис. 3. После того, как процесс А выдаст запрос Send(), он не сможет выполняться до тех пор, пока не получит ответа на переданное им сообщение. Это служит гарантией того, что обработка данных, выполняемая процессом В для процесса А завершится до того, как процесс А сможет продолжить свою работу. В свою очередь процесс В после выдачи запроса Receive() может продолжать свою работу до поступления другого сообщения.

Более подробно механизм планирования работы процессов рассмотрен в подразделе 2.7 "Планирование процессов".



Системные процессы


Все функции, выполняемые операционной системой QNX, за исключением функций ядра, реализуются стандартными процессами. В типичной конфигурации системы QNX имеются следующие системные процессы:

Администратор процессов (Proc); Администратор файловой системы (Fsys); Администратор устройств (Dev); Сетевой администратор (Net).



Системные процессы и процессы пользователя


Системные процессы практически ничем не отличаются от любого процесса пользователя: у них нет специального или скрытого интерфейса, недоступного процессу пользователя.

Именно такая архитектура обеспечивает системе QNX неограниченную расширяемость. Поскольку большинство функций QNX выполняется стандартными системными процессами, то расширить операционную систему совсем не сложно: достаточно написать и включить в систему программу, реализующую новую функцию ОС.

Действительно, грань между операционной системой и прикладными программами весьма условна. Единственным принципиальным отличием системных процессов от прикладных является то, что системные процессы управляют ресурсами системы, предоставляя их прикладным процессам.

Предположим, что вы написали сервер базы данных. Как следует классифицировать эту программу?

Сервер базы данных должен выполнять функции, аналогичные функциям Администратора файловой системы, который получает запросы (сообщения) на открытие файлов и чтение или запись данных. Несмотря на то, что запросы к серверу базы данных могут быть более сложными, и в том и в другом случае формируется набор примитивов (посредством сообщений), в результате чего обеспечивается доступ к системному ресурсу. В обоих случаях речь идет о процессах, которые могут быть написаны конечным пользователем и выполняться по необходимости. Таким образом, сервер базы данных можно рассматривать как системный процесс в одном случае и как прикладной в другом. Фактически нет никакой разницы. Важно отметить, что в системе QNX подобные процессы включаются без каких бы то ни было модификаций других компонентов операционной системы.



Состояния блокировок


Когда выполнение процесса запрещено до момента завершения некоторой части протокола обмена сообщениями, говорят, что процесс "блокирован". Состояния блокировок процессов приведены в следующей таблице.

Если процесс выдал Процесс является
Запрос Send(), и отправленное им сообщение еще не получено процессом-получателем SEND-блокированным
Запрос Send(), и отправленное им сообщение получено процессом-получателем, но ответ еще не выдан REPLY-блокированным
Запрос Receive(), но сам еще не получил сообщение RECEIVE-блокированным

Состояния процесса в типичной транзакции Send-Receive-Reply представлены на рис. 4.

Рис. 4

Другие возможные состояния процессов рассмотрены в подразделе 3.3.



Создание


Создание процесса заключается в присвоении идентификатора процесса (ID) новому процессу и задании информации, определяющей программную среду нового процесса. Большая часть этой информации наследуется от "родителя" нового процесса (см. предыдущий параграф).



Создание специальных имен устройств


Можно также использовать альтернативные префиксы для создания специальных имен устройств. Например, если Spooler печати работает на узле 20, то можно изменить имя своего принтера на альтернативное следующим образом

/dev/printer=//20/dev/spool

Любой запрос на открытие /dev/printer будет перенаправлен по сети к реальному Spooler печати. Аналогично, если у вас нет своего накопителя на гибких магнитных дисках, то вы можете обратиться к дисководу на узле 20, используя следующий альтернативный префикс

/dev/fd0=//20/dev/fd0

В обоих рассмотренных выше случаях альтернативную переадресацию можно не выполнять, а к удаленному ресурсу обращаться непосредственно

//20/dev/spool или //20/dev/fd0



Создание таймеров


Процесс может создать один или несколько таймеров. Таймеры могут быть любого поддерживаемого системой типа, а их количество ограничивается максимально допустимым количеством таймеров в системе (см. утилиту Proc в "Утилитах").

Для создания таймера используется функция Си mktimer(). Эта функция позволяет задавать следующие типы механизма ответа на события:

перейти в режим ожидания до завершения. Процесс будет находиться в режиме ожидания начиная с момента установки таймера до истечения заданного интервала времени; оповестить с помощью proxy. Proxy используется для оповещения процесса об истечении времени ожидания; оповестить с помощью сигнала. Сформированный пользователем сигнал выдается процессу по истечении времени ожидания.



Spawn()


Примитив spawn() создает новый процесс по принципу "отец"-"сын". Это позволяет избежать использования примитивов fork() и exec(), что ускоряет обработку и является более эффективным средством создания новых процессов. В отличие от fork() и exec(), которые по определению создают процесс на том же узле, что и порождающий процесс, примитив spawn() может создавать процессы на любом узле сети.



Специфические для консоли функции


Помимо стандартных терминальных функций QNX (см. Руководство пользователя) драйвер консоли работает со специфическими для консоли функциями, которые позволяют прикладным процессам взаимодействовать с драйвером консоли напрямую посредством сообщений. Связь устанавливается функцией Си console_open(). После установления связи процесс QNX имеет следующие возможности.

Процесс может Посредством функции Си
Считывать данные непосредственно с экрана консоли console_read()
Выводить данные непосредственно на экран консоли console_write()
Быть асинхронно оповещенным о наступлении определенных событий (изменение отображаемых данных, перемещение курсора, изменение отображаемых размеров, смена видимой консоли и т.д.) console_arm()
Управлять размером консоли console_size()
Переключить видимую консоль console_active()

Драйвер консоли QNX выполняется как обычный процесс. Вводимые с клавиатуры данные преобразуются обработчиком прерываний клавиатуры и помещаются непосредственно во входную очередь. Выходные данные принимает и отображает драйвер Dev.con, пока он существует в качестве процесса.



Связь между процессами


В типичной многозадачной среде, при одновременном выполнении нескольких процессов реального времени, операционная система должна обеспечивать возможность взаимодействия процессов друг с другом.

Связь между процессами (interprocess communication - IPC) является ключом к разработке приложений, представляющих собой набор взаимосвязанных процессов, в котором каждый процесс выполняетет одну строго определенную функцию.

В QNX реализован простой, но мощный набор IPC-возможностей, благодаря которому значительно упрощается разработка приложений, представляющих набор взаимодействующих процессов.



Связь между процессами


Ядро QNX поддерживает три типа связи между процессами: сообщениями, proxy и сигналами.

Основной формой IPC в системе QNX является обмен сообщениями. Сообщения обеспечивают синхронную связь между взаимодействующими процессами, при которой процесс, посылающий сообщение, требует подтверждения о его приеме и, возможно, ответа на сообщение.

Связь посредством proxy представляет собой особый вид передачи сообщений. Этот вид связи специально предназначен для оповещения о событиях, при которых процесс-отправитель не нуждается во взаимодействии с процессом-получателем.

Связь сигналами - это традиционная форма IPC. Сигналы используются для обеспечения асинхронной связи между процессами.



Связь между процессами посредством proxy


Proxy представляет собой форму неблокирующей передачи сообщений, специально предназначенную для оповещения о событиях, при которых процесс-отправитель не нуждается во взаимодействии с процессом-получателем. Единственной функцией proxy является посылка фиксированного сообщения процессу, создавшему proxy. Так же, как и сообщения, proxy работают по всей сети.

Благодаря использованию proxy, процесс или обработчик прерываний может послать сообщение другому процессу, не блокируясь и не ожидая ответа. Ниже приведены некоторые примеры использования proxy:

процесс оповещает другой процесс о наступлении некоторого события, не желая при этом оставаться SEND-блокированным до тех пор, пока получатель не выдаст Receive() и Reply(); процесс посылает данные другому процессу, но не требует ни ответа, ни другого подтверждения о том, что получатель принял сообщение; обработчик прерываний оповещает процесс о том, что некоторые данные доступны для обработки.

Proxy создаются с помощью функции qnx_proxy_attach(). Любой процесс или обработчик прерываний, которому известен идентификатор proxy, может воспользоваться функцией Trigger() для того, чтобы выдать заранее определенное сообщение. Запросами Trigger() управляет ядро.

Процесс proxy может быть запущен несколько раз: выдача сообщения происходит каждый раз при его запуске. Процесс proxy может накопить в очереди для выдачи до 65535 сообщений.

Обслуживаемый процесс трижды обратился к proxy, в результате чего обслуживающий процесс получил три "законсервированных" сообщения от proxy.

Рис. 7



Связь между процессами посредством сигналов


Связь посредством сигналов представляет собой традиционную форму асинхронного взаимодействия, используемую в различных операционных системах.

В системе QNX поддерживается большой набор POSIX-совместимых сигналов, специальные QNX-сигналы, а так же исторически сложившиеся сигналы, используемые в некоторых версиях системы UNIX.



Связь между процессами посредством сообщений


В системе QNX под сообщением понимается пакет байтов, который синхронно передается от одного процесса к другому. Для самой системы содержимое сообщения не имеет никакого значения. Данные в сообщении имеют смысл только для отправителя и получателя.



Связи и индексные дескрипторы


В системе QNX к файлу данных можно обращаться, используя более одного имени файла. Каждое имя называется связью. (Фактически существует два вида связей: жесткие связи, которые мы будем называть просто "связями" и символические связи. Символические связи описаны в следующем разделе.)

Для поддержки связей каждого файла имя файла отделяется от информации, описывающей файл. Информация, не относящаяся к имени файла, хранится в структуре, называемой "индексным дескриптором" (inode).

Если файл имеет только одну связь (т.е. одно имя файла), информация индексного дескриптора (т.е. информация, не относящаяся к имени файла) хранится в элементе каталога данного файла.

Если файл имеет более одной связи, индексный дескриптор хранится как запись в специальном файле с именем /.inodes.

Обратите внимание на то, что связь файла можно создать только в том случае, если файл и связь находятся в одной и той же файловой системе.

На один и тот же файл ссылаются две связи с именами "more" и "less".

Рис. 21

Существует еще две ситуации, при которых файл может иметь вход в файл /.inodes:

если имя файла превышает 16 символов, то служебная информация хранится в файле /.inodes, оставляя в элементе каталога место для 48-символьного имени файла; если файл имел более одной связи, и все связи, кроме одной, были удалены, то за файлом остается отдельный элемент в файле /.inodes. В противном случае было бы невозможно найти элемент каталога, указывающий на элемент inode. (Обратной ссылки из inode к элементу каталога не существует.) Эту ситуацию можно исправить с помощью утилиты chkfsys.

Если вы хотите Используйте
Создать связь из интерпретатора Shell Утилиту ln
Создать связь из программы Функцию Си link()



Связи каталога


Для каталога нельзя создать жесткие связи. Однако, каталоги имеют две жестко определенные связи:

. - "точка";

..    - "точка точка".

Имя файла "точка" ссылается на предшествующий каталог, заданный в составном имени, а "точка точка" ссылается на предыдущий предшествующему каталогу.

/usr/home/fred/./test --------> /usr/home/fred/test --- | предшествующий

/usr/home/fred/../eric -------> /usr/home/eric --- --- | | предшествующий | | предыдущий предшестующему

Если нет предшествующего каталога, то "точка" ссылается на текущий каталог. Точно также, элемент "точка точка" после символа "/" означает просто "/", т.к. вы не можете выйти за пределы пути.



Текущий рабочий каталог


Если текущий рабочий каталог имеет два ведущих слэша (сетевой корень), говорят, что он определен и привязан к пространству путевых имен определенного узла. Если, наоборот, он имеет один ведущий слэш, то к нему добавляется сетевой корень, используемый по умолчанию. Например, команда

cd //18/

иллюстрирует первую (определенную) форму и привязывает все относительные составные имена к узлу 18, независимо от того, какой сетевой корень используется по умолчанию. Соответственно, вводя cd dev, в результате получим //18/dev.

Команда cd/ служит примером второй формы, в которой результирующее составное имя, получаемое из относительного, будет зависеть от сетевого корня, используемого по умолчанию. Например, если по умолчанию используется сетевой корень //9, то вводя команду cd dev, в результате получим //9/dev.

На самом деле все это не так сложно, как может показаться. Обычно сетевые корни (//node/) не определены, и вы работаете внутри своего пространства имен (которое определяется вашим сетевым корнем, используемым по умолчанию).

Большинство пользователей, регистрируясь в системе, не меняют сетевой корень, который устанавливается системой по умолчанию, т.е. работают в пространстве имен своего узла.



Удаление связей


При создании файла счетчику его связей присваивается единичное значение. По мере добавления связей значение счетчика увеличивается, при удалении - уменьшается. Область файла не удаляется с диска до тех пор, пока значение счетчика связей не станет равным нулю, и все программы, использующие файл, не закроют его. Это позволяет продолжать использовать открытый файл, даже если у него удалены все связи.

Если вы хотите Используйте
Удалить связи из интерпретатора Shell Утилиту rm
Удалить связь из программы Функции Си remove() или unlink()



Удаление таймеров


Для удаления таймера воспользуйтесь функцией Си rmtimer(). Таймер может удалить сам себя по истечении временного интервала при условии:

при вызове rmtimer() включена опция _TNOTIFY_SLEEP; таймер неповторяемый.



Управление устройствами


Драйверы устройств либо записывают принятые данные во входную очередь, либо получают данные из выходной очереди. Администратор Dev определяет, в каком случае требуется завершить передачу, требуется ли отображение вводимых данных на дисплее и т.д.

Для обеспечения высокой степени реактивности Администратор Dev должен выполняться с достаточно высоким приоритетом. Обычно администратору Dev не приходится сильно загружать процессор, поэтому он незначительно влияет на общую производительность системы.

Драйверы, подобно любым другим процессам QNX, могут выполняться с различными приоритетами в зависимости от особенностей обслуживаемых ими технических средств.

Управление устройствами на низшем уровне реализовано посредством вызова соответствующего драйвера по входу ioctl. Общий набор ioctl-команд поддерживается большинством драйверов, используемых непосредственно Администратором Dev. Кроме того, процессы QNX могут через Dev выдавать драйверам ioctl-команды, ориентированные на устройства (посредством функции Си qnx_ioctl()).



Управление временем


В QNX управление временем основано на использовании системного таймера. Этот таймер содержит текущее координатное универсальное время (UTC) относительно 0 часов 0 минут 0 секунд 1 января 1970 г. Для установки местного времени функции управления временем используют переменную среды TZ (описана в Руководстве пользователя).



Управляемый процессами приоритет


Приоритет Администратора файловой системы может определяться приоритетом процесса, посылающего сообщение. Когда Администратор файловой системы принимает сообщение, то его приоритет устанавливается равным приоритету процесса, пославшего сообщение.

Более подробную информацию см. в подразделе 2.7 "Планирование процессов".



Управляющие блоки открытия


Управляющий блок открытия (OCB) содержит текущую информацию об открываемом ресурсе. Например, файловая система сохраняет текущий указатель поиска в файле. Каждая функция open() создает новый ОСВ. Поэтому, если процесс открывает один и тот же файл дважды, то любые вызовы lseek(), использующие один FD, не будут влиять на указатель поиска для другого FD. То же самое происходит, когда разные процессы открывают один и тот же файл.

На рис. 17 схематически изображены два процесса, один из которых открывает два раза, а другой - один раз тот же самый файл. Для каждого процесса создаются свои дескрипторы файлов.

Процесс A открывает файл /tmp/file два раза. Процесс B открывает тот же файл один раз.

Рис. 17

Несколько дескрипторов файлов одного или более процессов могут ссылаться на один и тот же ОСВ. Это достигается двумя способами:

процесс может использовать функции Си dup(), dup2(), fcntl() для создания второго дескриптора файла, который ссылается на тот же ОСВ; при создании нового процесса посредством функций fork(), spawn() или exec() все дескрипторы файлов по умолчанию наследуются новым процессом; эти наследуемые дескрипторы ссылаются на те же ОСВ, что и соответствующие дескрипторы породившего процесса.

Когда несколько дескрипторов файлов ссылаются на один и тот же ОСВ, то любое изменение состояния ОСВ немедленно становится "видимым" всем процессам, имеющим дескрипторы файлов, связанные с данным ОСВ.

Например, если некоторый процесс использует функцию lseek() для изменения положения указателя поиска, то чтение или запись происходит с новой позиции указателя, и при этом совершенно не важно, какой дескриптор файла используется.

На рис. 18 показаны два процесса, один из которых открывает файл дважды, а затем с помощью функции dup() - третий раз. Затем процесс порождает другой процесс, который наследует все открытые файлы.

Процесс дважды открывает файл, а затем получает еще один FD с помощью функции dup(). Порожденный процесс наследует все три дескриптора.

Рис. 18

Можно запретить наследование дескрипторов файлов процессами, создаваемыми функциями spawn() или exec(), с помощью функции fcntl(), установив флаг FD_CLOEXEC.



Условный прием сообщений


Обычно для приема сообщения используется функция Receive(). Этот способ приема сообщений в большинстве случаев является наиболее предпочтительным.

Однако, иногда процессу требуется предварительно "знать", было ли ему послано сообщение, чтобы не ожидать поступления сообщения в RECEIVE-блокированном состоянии. Например, процессу требуется обслуживать несколько высокоскоростных устройств, не способных генерировать прерывания, и кроме того, процесс должен отвечать на сообщения, поступающие от других процессов. В этом случае используется функция Creceive(), которая считывает сообщение, если оно становится доступным, или немедленно возвращает управление процессу, если нет ни одного отправленного сообщения.

По возможности следует избегать использования функции Creceive(), так как она позволяет процессу непрерывно загружать процессор на соответствующем приоритетном уровне.



Установка периода таймера


Период таймера задается утилитой ticksize или функцией Си qnx_timerperiod(). Вы можете выбрать период в интервале от 500 микросекунд до 50 миллисекунд.



Установка таймеров


Вы можете задать таймеру следующие временные интервалы:

абсолютный. Время относительно 0 часов, 0 минут, 0 секунд, 1 января 1970 г.; относительный. Время относительно значения текущего времени.

Можно также задать повторение таймера на заданном интервале. Например, вы установили таймер на 9 утра завтрашнего дня. Его можно установить так, чтобы он срабатывал затем каждые пять минут после истечения этого времени. Можно также установить новый временной интервал существующему таймеру. Результат этой операции зависит от типа заданного интервала:

для абсолютного таймера новый интервал замещает текущий интервал времени; для относительного таймера новый интервал добавляется к оставшемуся временному интервалу.

Для установки Используйте
Абсолютного временного интервала Функцию abstimer()
Относительного временного интервала Функцию reltimer()



Устройства с параллельным интерфейсом


Параллельными портами принтера управляет драйвер Dev.par. При запуске Dev.par в командной строке задается аргумент, определяющий, какой параллельный порт установлен. Для того, чтобы узнать, доступен ли параллельный порт, используйте утилиту ls

ls /dev/par*

Dev.par - это драйвер вывода, поэтому у него нет входных или канонических входных очередей. При запуске Dev.par размер выходного буфера задается с помощью аргументов командной строки. Можно создать выходной буфер очень большого размера, например, в случае создания программного буфера печати.

Dev.par является примером управляемого полностью без прерываний сервера ввода/вывода. Обычно этот процесс находится в RECEIVE-блокированном состоянии, ожидая появления данных в своей выходной очереди, и запускается администратором Dev. Когда данные становятся доступными для печати, Dev.par начинает выполняться в цикле активного ожидания (с относительно низким адаптивным приоритетом), ожидая приема данных принтером. Такой низкоприоритетный цикл активного ожидания не влияет на общую производительность системы, а в среднем обеспечивает максимально возможную пропускную способность для устройства с параллельным интерфейсом.



Устройства с последовательным интерфейсом


Последовательными коммуникационными каналами управляет процесс Dev.ser. Этот драйвер может управлять несколькими физическими каналами, работая с периферийными устройствами, которые имеют имена /dev/ser1, dev/ser2 и т.д.

При запуске Dev.ser в командной строке можно задать аргументы, которые определяют типы и количество установленных последовательных портов. Для того, чтобы узнать, какие последовательные порты доступны, используйте утилиту ls

ls /dev/ser*

Dev.ser служит примером сервера ввода/вывода, управляемого только прерываниями. После инициализации аппаратного обеспечения процесс переходит в режим ожидания. Принимаемые прерывания помещают входные данные непосредственно во входную очередь. Первый выводимый символ передается через канал физическому устройству после запуска драйвера Администратором Dev. Последовательные символы передаются при поступлении соответствующих прерываний.



Увеличение размера файла


Когда Администратору файловой системы требуется увеличить размер файла, последний экстент которого уже заполнен, то он сначала пытается увеличить последний экстент хотя бы на один блок. Если это не удается сделать, то для расширяемого файла заводится новый экстент.

Для размещения нового экстента Администратор файловой системы действует по принципу "первый пригодный". Специальная таблица Администратора файловой системы содержит элементы для каждого блока, представленного в файле /.bitmap (этот файл описан в разделе "Ключевые компоненты раздела QNX"). Каждый из этих элементов определяет наибольший непрерывный свободный экстент в области, определяемой соответствующим блоком. Администратор файловой системы выбирает первый элемент данной таблицы, достаточный по размерам для нового экстента.



Виртуальные каналы


В системе QNX приложение может взаимодействовать с процессом, выполняющимся на другом компьютере сети, так же как с процессом, выполняющемся на своем компьютере. В самом деле, с точки зрения приложения нет никакой разницы между локальными и удаленными ресурсами.

Такая высокая степень прозрачности обеспечивается благодаря использованию виртуальных каналов, которые являются путями, по которым Сетевой администратор передает сообщения и сигналы по всей сети.

Виртуальные каналы (ВК) способствуют эффективному использованию ресурсов во всей сети QNX по нескольким причинам:

при создании виртуального канала имеется возможность задать работу с сообщениями определенной длины: это означает, что вы можете распределить ресурсы для обработки сообщения. Тем не менее, если потребуется послать сообщение, длина которого превышает максимально заданную, виртуальный канал автоматически изменит установленный максимальный размер буфера в соответствии с длиной передаваемого сообщения; если два процесса, находящиеся на разных узлах, взаимодействуют между собой более, чем через один виртуальный канал, виртуальные каналы разделяются во времени, так как между процессами существует только один реальный виртуальный канал. Эта ситуация часто возникает, когда процесс обращается к нескольким файлам удаленной файловой системы; если процесс подключается к существующему разделенному виртуальному каналу и запрашивает размер буфера больший, чем тот, который используется в данное время, размер буфера автоматически увеличивается; когда процесс завершается, все связанные с ним виртуальные каналы освобождаются.



Виртуальные процессы


Процесс-отправитель отвечает за установку виртуального канала между собой и процессом, с которым устанавливается связь. Для этого процесс-отправитель обычно вызывает функцию 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.

Связь по сети осуществляется посредством виртуальных каналов. Когда процесс PID1 посылает сообщение VID2 запрос send проходит по виртуальному каналу, в результате чего PID2 получает сообщение от VID1.

Рис. 8

Каждый VID обеспечивает соединение, которое содержит следующую информацию:

локальный pid; удаленный pid; удаленный nid (идентификатор узла); удаленный vid.

Вряд ли вам придется работать с виртуальным каналом напрямую. Если приложению требуется, например, получить доступ к удаленному ресурсу ввода/вывода, то виртуальный канал создается вызываемой библиотечной функцией open(). Приложения непосредственно не участвуют в создании или использовании виртуального канала. Если приложение определяет нахождение обслуживающего его процесса с помощью функции qnx_name_locate(), то виртуальный канал создается автоматически при вызове функции. Для приложения виртуальный канал просто отождествляется с PID.

Более подробная информация о функции qnx_name_locate() содержится в подразделе 3.4.



Вложенные прерывания


Поскольку архитектура микрокомпьютера позволяет присваивать аппаратным прерываниям приоритеты, то высокоуровневые прерывания могут вытеснять низкоуровневые.

Этот механизм полностью поддерживается в системе QNX.

В предыдущих примерах описаны простейшие и наиболее обычные ситуации, когда поступает только одно прерывание. Практически такие же временные характеристики имеют прерывания с высшим приоритетом. При расчете наихудших временных показателей для низкоприоритетных прерываний следует учитывать время обработки всех высокоприоритетных прерываний, так как в системе QNX высокоприоритетные прерывания вытесняют низкоприоритетные.

На рис. 14 представлен пример прерывания процесса.

Методы планирования

Выполняется процесс А. Прерывание IRQx запускает обработчик прерываний Intx, который вытесняется прерыванием IRQy и его обработчиком Inty. Inty вызывает "срабатывание" proxy, которое запускает процесс В, а Intx вызывает "срабатывание" proxy, запускающее процесс С.

Рис. 14



Восстановление файловой системы


Даже в самых хороших системах могут происходить следующие отказы:

появление на диске сбойных блоков вследствие бросков по питанию; неопытный или злонамеренный пользователь, имея доступ к привелегиям суперпользователя может реинициировать файловую систему (посредством утилиты dinit); из-за ошибок программа (особенно работающая не в среде QNX) может проигнорировать информацию о разделении диска и перезаписать часть раздела QNX.

Поэтому, для того, чтобы можно было восстановить как можно больше файлов в случае возникновения указанных отказов, на диск записываются уникальные "метки", которые помогают при автоматической идентификации и восстановлении критических частей файловой системы. Файл индексных дескрипторов (/.inodes), а также каждый каталог и блок экстентов содержат уникальные структуры данных, которые утилита chkfsys использует для восстановления поврежденной файловой системы.

Более подробно о восстановлении файловой системы смотрите в описании утилиты chkfsys.



Временные файлы


В QNX имеется возможность открывать временные файлы, которые записываются, а затем считываются в течение короткого интервала времени. Для таких файлов Администратор файловой системы стремится сохранить блоки данных в кэш-буфере и записывает блоки на диск только в случае крайней необходимости.



Ядро операционной системы QNX, представленное


Ядро операционной системы QNX, представленное на рис. 2, выполняет следующие функции:
связь между процессами (IPC). Ядро обеспечивает три формы IPC (сообщения, proxy (прокси) и сигналы); сетевое взаимодействие нижнего уровня. Ядро передает все сообщения, предназначенные процессам на другом узле; планирование процессов. Планировщик ядра определяет, какой процесс будет выполняться следующим; первичную обработку прерываний. Все прерывания и сбои аппаратного обеспечения сначала обрабатываются в ядре, а затем передаются соответствующему драйверу или системному администратору.



Микроядро

Рис. 2



вывода не являются частью ядра.


Ресурсы ввода/ вывода не являются частью ядра. Обслуживающие программы ввода/вывода запускаются динамически при работе системы. Поскольку файловая система QNX является необязательным элементом, то пространство составных имен не является, как в большинстве монолитных операционных систем, частью файловой системы.


позволяет стандартным образом организовать хранение


Администратор файловой системы (Fsys) позволяет стандартным образом организовать хранение и получение доступа к данным дисковых подсистем.
Администратор Fsys отвечает за обработку всех запросов на открытие, закрытие, чтение и запись файлов.


и периферийными устройствами. Имена периферийных


Администратор устройств (Dev) обеспечивает интерфейс между процессами и периферийными устройствами. Имена периферийных устройств принадлежат пространству имен ввода/вывода и начинаются с префикса /dev. Например, консоль в системе QNX имеет имя /dev/con1.


расширяет возможности операционной системы QNX


Сетевой администратор (Net) расширяет возможности операционной системы QNX в части передачи сообщений по сети.
Взаимодействуя непосредственно с ядром, Сетевой администратор расширяет возможности IPC, реализованные в системе QNX на основе передачи сообщений, обеспечивая эффективный обмен с удаленными машинами. Кроме того, Сетевой администратор обеспечивает:
увеличение пропускной способности посредством баллансировки нагрузки; отказоустойчивость посредством избыточной связности.


Выбор сети


В том случае, когда узлы объединены более чем одной логической сетью, Сетевому администратору приходится выбирать, какую сеть использовать. Например, на приведенном выше рисунке узел 7 может передать данные узлу 8, используя драйвер, подключенный к сети 1 или к сети 2.



Выполнение


Как только программный код загружен, процесс готов к выполнению; он начинает конкурировать с другими процессами, стремясь получить ресурсы центрального процессора. Обратите внимание на то, что процессы выполняются конкурентно вместе со своими "родителями". Кроме того, гибель "родителя" не вызывает автоматически гибель порожденных им процессов.



Задержка планирования


В некоторых случаях низкоприоритетный обработчик аппаратных прерываний должен планировать выполнение высокоприоритетных процессов. В этом случае обработчик прерываний возвращает управление и вызывает срабатывание proxy. Это и есть вторая форма задержки - задержка планирования, которую мы рассмотрим ниже.

Задержка планирования - это время между завершением работы обработчика прерываний и началом выполнения первой команды управляющего процесса. Обычно это интервал времени, который требуется для сохранения контекста процесса, выполняющегося в данный момент времени, и восстановления контекста управляющего процесса. Несмотря на то, что это время больше задержки прерывания, оно также остается небольшим в системе QNX.

На рис. 13 представлена диаграмма задержки планирования.

Методы планирования

Обработчик прерываний завершает работу и инициирует "срабатывание" proxy. Времена даны для процессора 386, 20 МГц в защищенном режиме.

Рис. 13

Важно заметить, что большинство обработчиков прерываний завершают работу без инициирования "срабатывания" proxy. В большенстве случаев обработчик прерываний сам справляется со всеми аппаратными событиями. Выдача proxy для подключения управляющего процесса более высокого уровня происходит только при возникновении особых событий. Например, обработчик прерываний драйвера устройства с последовательным интерфейсом, передающий один байт данных аппаратуре, должен на каждое принятое прерывание на передачу запустить высокоуровневый процесс (Dev) только в том случае, если выходной буфер в итоге окажется пустым.



Задержка прерывания


Задержка прерывания - это интервал времени между приемом аппаратного прерывания и началом выполнения первой команды обработчика данного прерывания. В системе QNX все прерывания открыты все время, поэтому задержка прерывания обычно незначительна. Но некоторые критические программы требуют, чтобы на время их выполнения прерывания были закрыты. Максимальное время закрытия прерывания обычно определяет худший случай задержки прерывания; следует отметить, что в системе QNX это время очень мало.

На рис._12 представлена диаграмма обработки аппаратного прерывания соответствующим обработчиком прерываний. Обработчик прерываний либо просто возвращает управление процессу, либо возвращает управление и вызывает "срабатывание" proxy.

Методы планирования

Обработчик прерываний нормально отрабатывает. Времена даны для процессора 386, 20 МГц в защищенном режиме.

Рис. 12

На диаграмме, приведенной выше, задержка прерывания (Til) представляет собой минимальную задержку, для случая, когда во время возникновения прерывания все прерывания были открыты. В худшем случае задержка равна этому времени плюс наибольшее время работы процесса QNX, когда прерывания закрыты.



Загрузка


Загрузка образов процессов выполняется загрузчиком "по цепочке". Загрузчик входит в состав Администратора процессов и выполняется под идентификатором нового процесса. Это позволяет Администратору процессов выполнять другие запросы при загрузке программ.



Зарезервированные коды сообщений


Все сообщения в системе QNX начинаются с 16-битового слова, которое называется кодом сообщения. Системные процессы QNX используют следующие коды сообщений:

0X0000 - 0X00FF сообщения Администратора процессов;
0X0100 - 0X01FF сообщения ввода/вывода (для всех обслуживающих программ);
0X0200 - 0X02FF сообщения Администратора файловой системы;
0X0300 - 0X03FF сообщения Администратора устройств;
0X0400 - 0X04FF сообщения Сетевого администратора;
0X0500 - 0X0FFF зарезервировано для системных процессов, которые могут появиться в будущем.



Завершение


Процесс завершается одним из двух способов:

по сигналу, определяющему процесс завершения; при возврате управления по функции exit()__явно, либо по функции main()_-_по умолчанию.

Завершение включает в себя две стадии:

Администратор процессов инициирует выполнение завершения "по цепочке". Программа завершения входит в состав Администратора процессов и выполняется под идентификатором завершающегося процесса. При этом выполняется закрытие всех описателей открытых файлов и освобождаются:

все виртуальные каналы, которые имел процесс; вся память, выделенная процессу; все символические имена; все номера основных устройств (только для администраторов ввода/вывода ); все обработчики прерываний; все proxy; все таймеры.

После запуска программы завершения к породившему его процессу посылается уведомление о завершении процесса (эта фаза выполняется внутри Администратора процессов).

Если породивший процесс не выдал 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.





Возможные состояния процесса в системе QNX.

Рис.15
Определены следующие переходы из одного состояния в другое:

Процесс посылает сообщение. Процесс-получатель принимает сообщение. Процесс-получатель отвечает на сообщение. Процесс ожидает сообщения. Процесс принимает сообщение. Сигнал разблокирует процесс. Сигнал пытаетcя разблокировать процесс; получатель запрашивает сообщение о захвате сигнала. Процесс-получатель принимает сигнал. Процесс ожидает завершения порожденного процесса. Порожденный процесс завершается, либо сигнал разблокирует процесс. Процессу выдан SIGSTOP. Процессу выдан SIGCONT. Процесс завершается. Порождающий процесс ожидает завершения, завершается сам или уже завершен.




Живучесть файловой системы


Организация файловой системы QNX обеспечивает высокую пропускную способность и высокую надежность. Это достигается несколькими путями.

Основной объем данных проходит через кэш-буфер и записывается на диск только после небольшой задержки, причем критичные для файловой системы данные записываются немедленно. Все обновления каталогов, индексных дескрипторов, блоков экстентов и битовых карт сразу же попадают на диск во избежание разрушения файловой системы на диске (т.е. никогда не должно возникнуть ситуации внутренней противоречивости данных на диске).

В некоторых случаях возникает необходимость обновления всех вышеназванных структур. Например, если при записи файла в каталог последний экстент каталога окажется заполненным, то размер каталога должен увеличиться. В этом случае порядок действий должен быть определен очень аккуратно для того, чтобы в случае глобального сбоя (например, при сбое по питанию), при частично завершенном действии, файловая система после перезагрузки осталась бы цела. В худшем случае, некоторые блоки могут быть отмечены как занятые, но не использованы. Их можно в дальнейшем освободить, запустив утилиту chkfsys.



Жизненный цикл процесса


Каждый процесс проходит следующие четыре фазы:

Создание; Загрузку; Выполнение; Завершение.