Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лек СРВ от Анн.doc
Скачиваний:
11
Добавлен:
09.11.2019
Размер:
2.26 Mб
Скачать

Лекция №5. Операционная система qnx. Системная архитектура.

План лекции.

1. Связь между процессами (IPC)

2. IPC посредством messages

3. Обмен сообщениями управляемый ответами

4. Продвинутые возможности по передачи сообщений.

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

QNX Kernel отвечает за осуществление следующих функций:

• связь между процессами— Kernel занимается роутингом messages; он так же управляет двумя другими формами IPC—proxies и Signals

• низкоуровневая сетевая связь— Kernel доставляет все сообщения, предназначенные для процессов на других нодах

• построение очереди процессов— Kernel scheduler решает, какой процесс будет выполняться следующим

• обработка прерываний на первом уровне – все аппаратные прерывания и ошибки сначала направляются через Kernel, затем передаются соответствующему драйверу или System manager

ПРОЦЕССЫ

ПРЕРЫВАНИЯ

Рис. QNX Microkernel.

1. Связь между процессами (IPC)

QNX Kemel поддерживает три необходимых вида связи между процессами: messages, proxies, и Signals.

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

Proxies -- это особая форма сообщений. Они особенно хорошо подходят для сообщений о событиях, где процесс-отправитель не требует взаимодействия с процессем-получателем.

Signals это традиционная форма IPC. Они нужны для поддержки несинхронной связи между процессами.

2. IPC посредством messages

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

Примитивы для передачи messages.

Для общения напрямую, кооперирующие процессы используют функции языка C:

• Send()—для посылки messages

• Receive()— для приема messages

• Reply()—для ответа процессам, которые послали messages

Эти функции могут быть использованы локально или в сети.

За исключением тех случаев, когда процессы хотят связываться напрямую, им не надо использовать Send(), Receive(), и Reply(). Библиотека QNX C построена поверх механизма сообщений — процессы используют этот механизм не на прямую, когда они используют стандартные возможности, такие как pipes.

Приведенный ниже пример отображает простейшую последовательность событий, в которой два процесса, А и В используют Send(), Receive(), и Reply() для связи друг с другом:

1) Процесс A посылает сообщение процессу B путем выдачи kernel-у запроса Send(). В этот момент процесс А становится заблокированным для дальнейших посылок сообщений до тех пор, пока процесс В не выдаст Receive() чтобы получить сообщение.

2) Процесс В выдает Receive() и получает ожидающее сообщение процесса А. Процесс А переходит в состояние "заблокирован для приема ответа". Процесс В не блокируется, так как это его ожидали.

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

3) Процесс В завершает обработку сообщения принятого от процесса А и выдает Reply(). Ответное сообщение копируется в процесс А который готов к дальнейшей работе. Reply() ничего не блокирует, поэтому процесс В тоже готов к дальнейшей работе. Кто из них дальше будет выполняться - зависит от их взаимных приоритетов.

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

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

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

Заблокированные состояния.

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

В таблице приведены возможные заблокированные состояния процессов:

Если процесс выдал:

то он:

Запрос Send(), и сообщение посланное им ещё не принято процессом-получателем

Запрос Send(), и сообщение было принято процессом-получателем, но он ещё не ответил

Запрос Receive(), но ещё не принял сообщения

Заблокирован для передачи (SEND-blocked)

Заблокирован для приема ответа (REPLY-blocked)

Заблокирован для приема сообщения (RECEIVE-blocked)

Состояния возможные при передал-принял-ответил операции.

Send() This process Send() Other process

Применение Send(), Receive(), и Reply()

Давайте теперь более подробно рассмотрим вызов функций Send(), Receive(), и Reply(). Воспользуемся примером с процессами А и В.

1. Send() Пусть процесс A выдает запрос на посылку сообщения процессу В.

Выдача запроса происходит посредством вызова функции Send():

Send( pid, smsg, rmsg, smsg_len, rmsg_len);

Вызов Send() содержит следующие аргументы:

  • pid  идентификатор процесса-получателя сообщения (например, Process B); pid это идентификатор, под которым процесс известен операционной системе и другим процессам

  • smsg  буфер сообщения (посылаемое сообщение)

  • rmsg  буфер ответа (тут будет ответ процесса В)

  • smsg_len  длина посылаемого сообщения

  • rmsg_len  максимальная длина ответа которую примет процесс А

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

2. Receive() Процесс В может принять Send() выданную процессом A путем вызова Receive():

pid = Receive (0, msg, msg Jen);

Вызов Receive() содержит следующие аргументы:

  • pid  возвращается идентификатор процесса-отправителя (например Process A) (ноль) обозначает, что процесс В готов принимать сообщения от всех процессов

  • msg  буфер, куда будет принято сообщение

  • msg _len  максимальный объем информации, который может быть принят в буфер приема

Если smsg_len в вызове Send() и msg_len в вызове Receive() различны по размеру - будет передан наименьший из указанных объемов информации.

3. Reply(). Успешно получив сообщение от процесса А, процесс В должен ответить процессу А вызвав функцию Reply():

Reply (pid, reply, reply_len);

Вызов Reply() содержит следующие аргументы:

  • pid  идентификатор процесса, которому предназначен ответ (например Process A)

  • reply  буфер ответа

  • reply_len  объем передаваемой в качестве ответа информации

Если reply_len в вызове reply() и rmsg_len в вызове Send() различны по размеру - будет передан наименьший из указанных объемов информации.

3. Обмен сообщениями управляемый ответами

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

Существует и другая форма обмена сообщениями. Хотя она используется и не так часто как предыдущая, иногда она является предпочтительней: обмен сообщениями управляемый ответами, действие которого начинается с Reply(). По этому методу клиентский процесс посылает серверу сообщение, что он готов выполнить работу. Сервер не обязан немедленно отвечать, он просто запоминает что "рабочий незанят". В некий момент времени в будущем сервер может произвести какое-либо действие использовав свободного "рабочего". Клиентский процесс выполнит работу и прекратит обмен сообщениями, отослав серверу результаты работы.

Вот ещё несколько вещей, которые надо знать об обмене сообщениями:

  • Содержание сообщения хранится в процессе отправителе до тех пор, пока получатель не будет готов обработать его. Сообщение НЕ КОПИРУЕТСЯ в Kernel. Это безопаснее, так как процесс-отправитель заблокирован на отправление и не может случайно исказить отправляемую информацию.

  • Информация, передаваемая как ответ копируется из отвечающего процесса в процесс заблокированный на ответ как atomic операция когда выдан запрос Reply(). Reply() не блокирует отвечающий процесс—процесс заблокированный на ответ освобождается после того как информация скопирована на свое место.

  • Для того чтобы послать сообщение отправляющему процессу не нужна информация о состоянии принимающего. Если принимающий процесс не готов получить уже посланное сообщение то процесс- отправитель просто блокируется на отправление.

  • При необходимости могут быть посланы сообщения и ответы нулевой длины.

С точки зрения разработчика выдача Send() процессу-серверу для получения обслуживания практически идентична вызову библиотечной subroutine для получения того же обслуживания. В любом случае сначала вы создаете некие структуры данных, затем вызываете либо Send() либо библиотеку. Затем исполняется весь код обслуживания (service code) , который расположен между двумя хорошо заметными точками—Receive() и Reply() для процесса-сервера или входом в функцию и возвращаемым значением для вызова библиотеки–ваш же код в это время ждет. Когда вызов обслуживания возвращается, ваш код "знает" где хранятся результаты и может продолжать работу, проверять результат на ошибки или делать что там ему еще вздумается.

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

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

4. Продвинутые возможности по передачи сообщений.

QNX так же предоставляет следующие продвинутые возможности по передачи сообщений:

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

• чтение или запись части сообщения

• сообщение из нескольких частей

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

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

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

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

Чтение или запись части сообщения

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

Например, менеджер ввода/вывода может принимать сообщения с данными, которые представляют собой заголовок фиксированного размера и информационную часть переменного размера. Заголовок сообщает об объеме данных (от 0 до 64Кбайт). Менеджер ввода/вывода может выбирать для получения только заголовок и затем использовать функцию С Readmsg(), чтобы читать данные переменной длины в подходящего размера буфер вывода. Если размер посланных данных больше буфера менеджера ввода/вывода, то он может выдать несколько Readmsg() запросов и переместить данные, как только появляется свободное место. Аналогично функция С Writemsg() может быть использована чтобы на протяжении времени получать данные, а затем копировать их в буфер отправителя как только там появляется место, уменьшая, таким образом, потребность менеджера ввода/вывода во внутреннем буфере.

Сообщение из нескольких частей

До сих пор сообщение рассматривалось как один пакет байтов. На самом деле сообщение часто состоит из двух и более отельных компонентов. Например сообщение может представлять собой заголовок фиксированного размера и данные переменного размера. Для того чтобы его составляющие могли эффективно передаваться и приниматься без копирования во временный рабочий буфер, из двух и более отдельных буферов может быть создано "сообщение из нескольких частей". Эта возможность позволяет менеджерам ввода/вывода QNX, таким как Dev и Fsys, достигать высоких показателей.

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

Creceivemx()

Readmsgmx()

Receivemx()

Replymx()

Sendmx()

Writemsgmx()

"message''

Сообщение из нескольких частей может быть описано с помощью mx control structure. Кernel собирает их в единый поток данных.

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

Хотя никто не требует, чтобы вы делали это, Quantum начинает все свои сообщения с 16-и битового слова называемого кодом сообщения. Обратите внимание, что системные процессы Quantum используют коды сообщений в следующих пределах:

0X0000

to 0X0OFF

сообщения менеджера процессов

0X0100

to0x0IFF

I/O сообщения (общие для всех I/O серверов)

0X0200

toOx02FF

Сообщения менеджера файловой системы

0X0300

to0x03FF

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

0X0400

to0x04FF

Сообщения менеджера сети

0X0500

toOXOFFF

Зарезервировано для будущих системных процессов Quantum