Файл: А. В. Гордеев А. Ю. Молчанов системное программное обеспечение электронный вариант книги издательства Питер СанктПетербург Челябинск юургу каф. Автоматика и управление 2002 2 Предисловие Настоящий учебник.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 12.01.2024

Просмотров: 1021

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

323
вому ящику. Почтовые ящики являются системными объектами, и для пользования таким объектом необходимо получить его у операционной системы, что осуществ- ляется с помощью соответствующих запросов.
Если объём передаваемых данных велик, то эффективнее не передавать их не- посредственно, а отправлять в почтовый ящик сообщение, информирующее про- цесс-получатель о том, где можно их найти.
Почтовый ящик может быть связан с парой процессов, только с отправителем,
только с получателем, или его можно получить из множества почтовых ящиков,
которые используют все или несколько процессов. Подовый ящик, связанный с процессом-получателем, облегчает посылку сообщений от нескольких процессов в фиксированный пункт назначения. Если почтовый ящик не связан жестко с про- цессами, то сообщение должно содержать идентификаторы и процесса–отправите- ля, и процесса-получателя.
Итак, почтовый ящик – это информационная структура, поддерживаемая опе- рационной системой. Она состоит из головного элемента, в котором находится ин- формация о данном почтовом ящике, и из нескольких буферов (гнёзд), в которые помещают сообщения. Размер каждого буфера и их количество обычно задаются при образовании почтового ящика.
Правила работы почтового ящика могут быть различными в зависимости от его сложности [37]. В простейшем случае сообщения передаются только в одном направлении. Процесс Р1 может посылать сообщения до тех пор, пока имеются свободные гнёзда. Если все гнёзда заполнены, то Р1 может либо ждать, либо за- няться другими делами и попытаться послать сообщение позже. Аналогично про- цесс Р2 может получать сообщения до тех пор, пока имеются заполненные гнёзда.
Если сообщений нет, то он может либо ждать сообщений, либо продолжать свою работу. Эту простую схему работы почтового ящика можно усложнять в несколь- ких направлениях и получать более хитроумные системы общения – двунаправ- ленные и многовходовые почтовые ящики.
Двунаправленный почтовый ящик, связанный с парой процессов, позволяет подтверждать приём сообщений. Если используется множество гнёзд, то каждое из

324
них хранит либо сообщение, либо подтверждение. Чтобы гарантировать передачу подтверждений, когда все гнёзда заняты, подтверждение на сообщение помещается в то же гнездо, которое было использовано для сообщения, и оно уже не использу- ется для другого сообщения до тех пор, пока подтверждение не будет получено.
Из-за того, что некоторые процессы не забрали свои сообщения, связь может быть приостановлена. Если каждое сообщение снабдить пометкой времени, то управ- ляющая программа может периодически уничтожать старые сообщения.
Процессы могут быть также остановлены в связи с тем, что другие процессы не смогли послать им сообщения. Если время поступления каждого остановленно- го процесса в очередь заблокированных процессов регистрируется, то управляю- щая программа может периодически посылать им пустые сообщения, чтобы они не ждали чересчур долго.
Реализация почтовых ящиков требует использования примитивных операто- ров низкого уровня, таких как Р- и V-операции, или каких-либо других средств, но пользователям может дать средства более высокого уровня (наподобие мониторов
Хоара), например, ввести следующие операции:
1 SEND_MESSAGE (Получатель, Сообщение, Буфер)
переписывает сообщение в некоторый буфер, помещает его адрес в перемен- ную Буфер и добавляет буфер к очереди Получатель. Процесс, выдавший опе- рацию SEND_MESSAGE, продолжит своё исполнение.
2 WAIT_MESSAGE (Отправитель, Сообщение, Буфер)
блокирует процесс, выдавший операцию, до тех пор, пока в его очереди не появится какое-либо сообщение. Когда процесс устанавливается на процессор, он получает имя отправителя в переменной Отправитель, текст сообщения – в Со- общение и адрес буфера – в Буфер. Затем буфер удаляется из очереди, и процесс может записать в него ответ отправителю.
3 SEND_ANSWER (Результат, Ответ, Буфер)
записывает Ответ в тот Буфер, из которого было получено сообщение, и до- бавляет буфер к очереди отправителя. Если отправитель ждёт ответ, он деблокиру- ется.


325 4 WAIT_ANSWER (Результат, Ответ, Буфер)
блокирует процесс, выдавший операцию, до тех пор, пока в Буфер не посту- пит ответ. После того как ответ поступил, и процесс установлен на процессор, От- вет переписывается в память процессу, а буфер освобождается. Результат указыва- ет, является ли ответ пустым, то есть выданным операционной системой, так как сообщение было адресовано несуществующему (или так и не ставшим активным)
процессу.
Основные достоинства почтовых ящиков:
♦ процессу не нужно знать о существовании других процессов до тех пор, по- ка он не получит сообщения от них;
♦ два процесса могут обмениваться более чем одним сообщением за один раз;
♦ операционная система может гарантировать, что никакой процесс не вме- шается в «беседу» других процессов;
♦ очереди буферов позволяют процессу-отправителю продолжать работу, не обращая внимания на получателя.
Основным недостатком буферизации сообщений является появление еще од- ного ресурса, которым нужно управлять, самих почтовых ящиков.
Другим недостатком можно считать статический характер этого ресурса: ко- личество буферов для передачи сообщений через почтовый ящик фиксировано.
Поэтому естественным стало появление механизмов, подобных почтовым ящикам,
но реализованных на принципах динамического выделения памяти под передавае- мые сообщения.
1   ...   22   23   24   25   26   27   28   29   ...   37

Конвейеры и очереди сообщений
Конвейеры (программные каналы)
Конвейер (pipe – программный канал (связи), или, как его иногда называют,
транспортер) является средством, с помощью которого можно производить обмен данными между процессами. Принцип работы конвейера основан на механизме ввода/вывода, который используется для работы с файлами в UNIX, то есть задача,
передающая информацию, действует так, как будто она записывает данные в файл,
в то время как задача, для которой предназначается эта информация, читает её из

326
этого файла. Операции записи и чтения осуществляются не записями, как это дела- ется в обычных файлах, а потоком байтов, как это было принято в UNIX-системах.
Таким образом, функции, с помощью которых выполняется запись в канал и чте- ние из него, являются теми же самыми, что и при работе с файлами. По сути, канал представляет собой поток данных между двумя (или более) процессами. Это упро- щает программирование и избавляет программистов от использования каких-то новых механизмов. На самом деле конвейеры не являются файлами на диске, а представляют собой буферную память, работающую по принципу FIFO, то есть по принципу обычной очереди. Однако не следует путать конвейеры с очередями со- общений; последние реализуются иначе и имеют другие возможности.
Конвейер имеет определенный размер
1
, который не может превышать 64
Кбайт, и работает циклически. Вспомните реализацию очереди на массивах, когда имеются указатели начала и конца очереди, которые перемещаются циклически по массиву. Имеется некий массив и два указателя: один показывает на первый эле- мент (назовем его условно, head), а второй – на последний (назовем его tail).
В начальный момент оба указателя равны нулю. Добавление самого первого элемента в пустую очередь приводит к тому, что указатели head и tail принимают значение, равное 1 (в массиве появляется первый элемент). В последующем добав- ление нового элемента вызывает изменение значения второго указателя, поскольку он отмечает расположение именно последнего элемента очереди. Чтение (и удале- ние) элемента (читается и удаляется всегда первый элемент из созданной очереди)
приводит к необходимости модифицировать значение указателя head. В результате операций записи (добавления) и чтения (удаления) элементов в массиве, модели- рующем очередь элементов, указатели будут перемещаться от начала массива к его концу. При достижении указателем значения индекса последнего элемента массива значение указателя вновь становится единичным (если при этом не произошло пе- реполнение массива, то есть количество элементов в очереди не стало больше чис- ла элементов в массиве). Можно сказать, что мы как бы замыкаем массив в кольцо,
организуя круговое перемещение указателей head и tail, которые отслеживают пер-
1
Pipe (канал или конвейер) был введен в UNIX-системах и имеет максимальный размер в 64 Кбайт, поскольку в 16- разрядных мини-ЭВМ, для которых создавалась эта система, нельзя было создать массив данных большего размера.


327
вый и последний элементы в очереди. Сказанное проиллюстрировано на рис. 6.5.
Именно так и функционирует конвейер.
Рис. 6.5. Организация очереди на массиве
Как информационная структура канал описывается идентификатором, разме- ром и двумя указателями. Конвейеры представляют собой системный ресурс. Что- бы начать работу с конвейером, процесс сначала должен заказать его у операцион- ной системы и получить в своё распоряжение. Процессы, знающие идентификатор конвейера, могут через него обмениваться данными.
Теперь рассмотрим основные системные запросы для работы с ними. В каче- стве примера возьмем вызовы из API OS/2 (в следующем разделе мы ими восполь- зуемся). Традиционные вызовы для работы с каналами (конвейерами) приведены в разделе, где описывается архитектура системы UNIX (см. главу 8). Итак:
♦ Функция создания конвейера:
DosCreatePipe (&ReadHand1e, &WriteHandle, PipeSize);
где ReadHandle – описатель для чтения из конвейера, WriteHandle – описа- тель для записи в конвейер, PipeSize – размер конвейера.
♦ Функция чтения из конвейера:
DosRead (&ReadHandle, (PVOID)&Inform, sizeof(Inform), &BytesRead);
где ReadHandle – описатель для чтения из конвейера, Inform – переменная любого типа, sizeof( Inform) – размер переменной Inform, BytesRead – количество

328
прочитанных байтов. Данная функция при обращении к пустому конвейеру будет ожидать, пока в конвейере не появится информация для чтения.
♦ Функция записи в конвейер:
DosWrite (&WriteHand1e, (PVOID)&Inform, s1zeof( Inform), &BytesWrite);
где WriteHandle – описатель для записи в конвейер, BytesWrite – количество записанных байтов.
Читать из конвейера может только тот процесс, который знает идентификатор соответствующего конвейера. При работе с конвейером данные непосредственно помещаются в него. Ещё раз отметим, что из-за ограничения на размер конвейера программисты сталкиваются и с ограничениями на размеры передаваемых через него сообщений.
Очереди сообщений
Очереди сообщений (Queue) являются более сложным методом связи между взаимодействующими процессами по сравнению с каналами. С помощью очередей также можно из одной или нескольких задач независимым образом посылать со- общения некоторой задаче-приёмнику. При этом только процесс-приёмник может читать и удалять сообщения из очереди, а процессы-клиенты имеют право лишь помещать в очередь свои сообщения. Таким образом, очередь работает только в одном направлении. Если же необходима двухсторонняя связь, то можно создать две очереди.
Работа с очередями сообщений имеет много отличий от работы с конвейера- ми. Во-первых, очереди сообщений предоставляют возможность использовать не- сколько дисциплин обработки сообщений:
♦ FIFO – сообщение, записанное первым, будет первым и прочитано;
♦ LIFO – сообщение, записанное последним, будет прочитано первым;
♦ приоритетный – сообщения читаются с учётом их приоритетов;
♦ произвольный доступ, то есть можно читать любое сообщение, тогда как канал обеспечивает только дисциплину FIFO.


329
Во-вторых, если при чтении сообщения из канала (конвейера) оно удаляется из него, то при чтении сообщения из очереди этого не происходит, и сообщение при желании может быть прочитано несколько раз,
В третьих, в очередях присутствуют не непосредственно сами сообщения, а только их адреса в памяти и размер. Эта информация размещается системой в сег- менте памяти, доступном для всех задач, общающихся с помощью данной очереди.
Каждый процесс, использующий очередь, должен предварительно получить разрешение на использование общего сегмента памяти с помощью системных за- просов API, ибо очередь – это системный механизм и для работы с ним требуются системные ресурсы и, соответственно, обращение к самой ОС. Во время чтения из очереди задача-приёмник пользуется следующей информацией:
♦ идентификатор процесса (PID – process ID), который передал сообщение;
♦ адрес и длина переданного сообщения;
♦ ждать или нет, если очередь пуста;
♦ приоритет переданного сообщения;
♦ номер освобождаемого семафора, когда сообщение передаётся в очередь.
Наконец, приведём перечень основных функций, управляющих работой оче- реди (без подробного описания передаваемых параметров), поскольку в различных
ОС обращения к этим функциям могут существенно различаться:
♦ CreateQueue – создание новой очереди;
♦ OpenQueue – открытие существующей очереди;
♦ ReadQueue – чтение и удаление сообщения из очереди;
♦ PeekQueue – чтение сообщения без его последующего удаления из очереди;
♦ WriteQueue – добавление сообщения в очередь;
♦ CloseQueue – завершение использования очереди;
♦ PurgeQueue – удаление из очереди всех сообщений;
♦ QueryQueue – определение числа элементов в очереди.

330
Примеры создания параллельных
взаимодействующих вычислительных
процессов
В завершение данного раздела рассмотрим учебный пример, в котором ста- вится задача создания комплекса параллельно выполняющихся взаимодействую- щих программ. Пример не будет иметь содержательного смысла в том плане, что никакой полезной работы программные модули, взаимодействующие между собой,
не выполняют. Они только синхронизируют друг друга по предопределенной схе- ме, демонстрируя главным образом способы организации взаимодействующих вы- числений – взаимное исключение при выполнении критических интервалов, обме- ны синхросигналами и данными.
Пусть в нашем примере каждая программа (при своём исполнении либо как самостоятельный вычислительный процесс, либо как задача) должна сообщить время начала своей работы, время окончания работы и, возможно, имена тех про- грамм, которые она (при определённых условиях) должна будет запланировать на выполнение.
Для иллюстрации различий в организаций взаимодействия полноценных вы- числительных процессов и многозадачных (многопотоковых) приложений приве- дём два примера реализации, что позволит увидеть разные механизмы.
Начнем с более простого случая, когда создается обычное мультитредовое приложение, причём воспользуемся не средствами API, а методами, специально созданными для системы программирования. Второй пример будет иллюстриро- вать применение более мощных средств для организации взаимного исключения и обмена сообщениями; здесь будут использованы средства самой ОС.
Пример создания многозадачного приложения с помощью системы про- граммирования Borland Delphi
Рассмотрим пример использования механизмов, специально созданных для организации взаимного исключения, которые имеются в системе программирова- ния Borland Delphi 3.0. Эта система программирования, будучи ориентированной на создание приложений в многозадачной операционной системе Microsoft Win-