Индекс
НазадОглавлениеВперед


Глава 9
Системные средства взаимодействия процессов

В предыдущей главе мы уже фактически затронули эту тему, однако, средства, которые мы рассматривали, в основном ограничивались теми, которые реализуются самим программистом или компилятором. Здесь мы сосредоточимся на тех средствах, использование которых поддерживается API ОС.

9.1. Скобки критических секций.

Выделение критических секций, как системное средство, целесообразно применять для относительно сильно связанных процессов - таких, которые разделяют большой объем данных. Кроме того, поскольку, как мы показали в предыдущей главе, при применении программистом скобок критических секций возможны ошибки, приводящие к подавлению одних процессов другими, важно, чтобы конфликты между процессами не приводили к конфликтам между пользователями. Эти свойства характерны для нитей - параллельно выполняющихся частей одного и того же процесса: они все принадлежат одному процессу - одному пользователю и разделяют почти все ресурсы этого процесса. Следовательно, критические секции целесообразно применять только для взаимного исключения нитей. ОС может предоставлять для этих целей элементарные системные вызовы, функционально аналогичные рассмотренным нами в предыдущей главе csBegin и csEnd. Когда нить входит в критическую секцию, все остальные нити этого процесса блокируются. Блокировка не затрагивает другие процессы и их нити. Естественно, что такая политика весьма консервативна и снижает уровень мультипрограммирования, но это может повлиять на эффективность только в рамках одного процесса. Программист может самостоятельно организовать и более либеральную политику доступа к разделяемым ресурсам, используя, например, семафоры, которые будут описаны ниже.

Кроме того, роль таких скобок могут играть системные вызовы типа suspend и release, первый из которых приостанавливает выполнение нити, а второй - отменяет приостановку.

9.2. Виртуальные прерывания или сигналы

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

    raiseInterrupt (pid, intType ); 

где pid - идентификатор процесса, которому посылается прерывание, intType - тип (возможно, номер) прерывания. Идентификатор процесса - это не внешнее его имя, а манипулятор, устанавливаемый для каждого запуска процесса ОС. Для того, чтобы процесс мог послать сигнал другому процессу, процесс-отправитель должен знать идентификатор процесса-получателя, то есть, находиться с ним в достаточно "конфиденциальных" отношениях. Чтобы предотвратить возможность посылки непредусмотренных прерываний, могут быть введены дополнительные ограничения: разрешить посылку прерываний только от процессов-предков к потомкам или ограничить обмен прерываниями только процессами одного и того же пользователя.

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

    setInterruptHandler (intType, action, procedure ); 
где action - вид реакции на прерывание. Вид реакции может задаваться из перечня стандартных, в число которых могут входить: реакция по умолчанию, игнорировать прерывание, восстановить прежнюю установку или установить в качестве обработчика прерывания процедуру procedure, адрес которой является параметром системного вызова.

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

Если процесс получает прерывание, для которого он не установил обработчик, то процесс должен аварийно завершиться (это - устанавливаемый по умолчанию вид реакции на прерывание). Такая установка может показаться чрезмерно жесткой, но вспомните, например, какова будет реакция системы на реальное прерывание, для которого не определен его обработчик (вектор прерывания в Intel-Pentium).

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

Как должна реагировать ОС на посылку прерывания несуществующему процессу? По-видимому, аварийное завершение процесса, выдавшего такое прерывание, может быть нормальной реакцией системы. Возможно, впрочем, и более либеральное решение - завершить вызов raiseInterrupt с признаком ошибки. Аналогичный эффект может вызвать выполнение прерывания, для которого в процессе-приемнике установлен специальный режим обработки - недопустимое прерывание.

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

В большинстве современных ОС (Unix, OS/2 и др.) виртуальные прерывания носят название сигналов и используются прежде всего для сигнализации о чрезвычайных событиях. Сигнальные системы конкретных ОС, как правило, не предоставляют в составе API универсального вызова типа raiseInterrupt, который позволял бы пользователю выдавать сигналы любого типа. Набор зарезервированных типов сигналов ограничен (в Unix, например, их 19, а в OS/2 - всего 7), не все из них доступны процессам, и для каждого из доступных имеется собственный системный вызов. Недопустимы также незарезервированные типы сигналов. В набор включается несколько (по 3 - в упомянутых ОС) типов сигналов, зарезервированных за процессами, эти типы и используют взаимодействующие процессы для посылки друг другу сигналов, которые они интерпретируют по предварительной договоренности.

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

9.3. Модель виртуальных коммуникационных портов

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

Концепция коммуникационных портов, однако, в реальных ОС выдерживается далеко не строго. Реально манипулирование далеко не со всем средствам взаимодействия между процессами возможно свести к однотипным операциям. Доступ к удаленным средствам решается методами сетевых модулей ОС. Разграничение доступа в полном объеме мы наблюдали только в AS/400, и то не в рамках файловой системы, а в контексте общей объектно-ориентированной структуры этой системы. Тем не менее, тенденция к модели портов в той или иной степени наблюдается в современных ОС, прежде всего, в части именования средств взаимодействия. В этом разделе мы рассмотрим общие свойства средств взаимодействия, называя их виртуальными коммуникационными портами.

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

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

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

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

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

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

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

9.4. Общие области памяти

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

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

    vAddr = createNamedMemorySegment(segmentName, segmentSize);

а второй ее "открывает":

    vAddr = openNamedMemorySegment(segmentName);

В этих вызовах segmentName - имя области, segmentSize - ее размер. Оба вызова возвращают виртуальный адрес общей области памяти в виртуальном адресном пространстве процесса - vAddr.

Для неименованной области памяти создание области осуществляется вызовом:

    vAddr = createMemorySegment(segmentSize);

а "открытие":

    vAddr = openMemorySegment(hostAddr, hostPid);

где hostAddr - виртуальный адрес области памяти у процесса-создателя области, hostPid - идентификатор процесса-создателя области. Этот вызов возвращает виртуальный адрес области в адресном пространстве процесса, открывшего область.

Разумеется, в составе API имеются системные вызовы "закрытия"/уничтожения общей области памяти.

Разделяемые области памяти, однако, порождают ряд проблем как для программистов, так и для ОС. Проблемы программистов - те, что рассматривались в предыдущем разделе: взаимное исключение процессов при доступе к общей памяти. Программисты могут дифференцировать права доступа для процессов или организовать взаимное исключение, используя семафоры (см. ниже). Проблемы ОС - организация свопинга. Очевидно, что вероятность вытеснения разделяемого сегмента или страницы должна быть тем меньше, чем больше процессов разделяют этот сегмент/страницу. Если каждый процесс имеет собственный дескриптор разделяемого сегмента или страницы, то учет использования сегмента или страницы (поля used и dirty) будут вестись по каждому процессу отдельно. ОС должна обрабатывать, например, такой случай: два процесса - A и B - разделяют сегмент; процесс A произвел запись в сегмент и в его дескрипторе сегмент помечен, как "грязный". В то время, когда активен процесс B, принимается решение о вытеснении этого сегмента из памяти. Но в дескрипторе процесса B этот сегмент имеет признак "чистый", поэтому сегмент может быть освобожден в физической памяти без сохранения на внешней памяти и изменения в сегменте, сделанные процессом A, будут утеряны. ОС приходится вести отдельную таблицу разделяемых сегментов, в которой отражать истинное их состояние.

С точки зрения идентификации разделяемые области памяти могут рассматриваться как виртуальные коммуникационные порты. Для общей области может быть по соглашению между разработчиками взаимодействующих процессов установлено внешнее имя, которое будет использовано для получения доступа. (В Windows 95, например, такие области называются "файлами отображаемой памяти" - memory mapped file - и для установления доступа к ним используются системные вызовы типа create и open). Для неименованных областей памяти возможна передача селектора (номера в таблице дескрипторов) от одного процесса к другому.

В системах, которые ориентированы на процессор Intel-Pentium, может использоваться то обстоятельство, что адресация возможна через две таблицы дескрипторов - LDT и GDT. За счет этого общее адресное пространство процесса может достигать 4 Гбайт. Из них младшие 2 Гбайт адресуются через LDT а старшие - через GDT. Глобальная таблица дескрипторов - общая для всех процессов, и именно она может использоваться для доступа к совместно используемой памяти. Размещение в общих виртуальных адресах удобно для системных программ и динамических библиотек, к которым происходят частые обращения из приложений: если эти компоненты ОС находятся в адресном пространстве процесса, то обращения к ним не требуют переключения контекста. Но с другой стороны, это снижает надежность: если системные компоненты доступны для приложения, то они могут быть им испорчены. Поэтому такая "роскошь" может быть допущена только в однопользовательских системах.

9.5. Семафоры

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

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

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

    flag = semaphoreOp(semaphorId, opCode, waitOption);
где semaphorId - манипулятор семафора, opCode - код операции, waitOption - опция ожидания, flag - возвращаемое значение, признак успешного выполнения операции или код ошибки.

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

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

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

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

9.6. Программные каналы

Программный канал по-английски называется pipe (труба), и это весьма удачное название. Канал действительно можно представить как трубопровод пневматической почты, проложенный между двумя процессами, как показано на рис.9.1. По этому трубопроводу данные передаются от одного процесса к другому. Как и трубопровод, программный канал однонаправленный (хотя, например, в Unix одним системным вызовом создаются сразу два разнонаправленных канала). Как и трубопровод, программный канал имеет собственную емкость: данные записанные в канал, не обязательно должны немедленно выбираться не противоположном его конце, но могут накапливаться в канале, пока это позволяет его емкость. Как и трубопровод, канал работает по дисциплине FIFO: первый вошел - первый вышел.


Рис.9.1. Программные каналы

Из всех средств взаимодействия между процессами каналы (pipe) лучше всего вписываются в модель виртуальных коммуникационных портов. Канал для процесса практически аналогичен файлу. Специальные системные вызовы типа createPipe, openPipe используются для создания канала и получения доступа к каналу, а для работы с каналом используются те же вызовы read и write, что и для файлов, и даже закрытие канала выполняется файловым системным вызовом close. При создании канала для него создается дескриптор, как для открытого файла, что позволяет работать с ним далее, как с файлом. Канал, однако, представляет собой не внешние данные, а область памяти. Для канала выделяется память в системной области, что может ограничивать емкость канала.

Наиболее часто используются неименованные каналы, как средство связи между родителем и потомком. Операция создания неименованного канала возвращает два файловых манипулятора: для чтения и для записи. Процесс-предок передает эти манипуляторы процессу-потомку. Если связь между предком и потомком однонаправленная, то каждый из них закрывает канал по одному из манипуляторов. Например, если данные передаются только от предка к потомку, то предок после передачи манипуляторов закрывает канал на чтение, а потомок - на запись. Пример установления такой связи в ОС Unix приведен в главе 11, в фрагменте программы командного интерпретатора.

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

Именованные каналы представляют собой удобное средство клиент-серверных коммуникаций. Именованные каналы в некоторых ОС (например, OS/2) существенно отличаются от неименованных. Именованные каналы ориентированы в этих системах прежде всего на взаимодействие процессов в сетевой среде (или, точнее, для них прозрачно, находятся ли оба процесса на одном компьютере или в разных узлах сети). Такой канал двунаправленный, то есть, к нему возможны обращения и для чтения, и для записи. Данные в канале могут передаваться как потоком байт, так и сообщениями. В последнем случае каждое сообщение снабжается заголовком, в котором указывается его длина. К одному концу канала постоянно подключен процесс, его создавший - владелец или сервер, к другому концу могут подключаться различные процессы-клиенты, таким образом, обмен данными по именованному каналу между процессами, ни один из которых не является владельцем канала, невозможен. При создании канала (системный вызов createNamedPipe) указывается максимально возможное число клиентов, которые могут быть одновременно подключены к каналу (это число, впрочем, может и не ограничиваться). Когда владелец создает канал, канал находится в так называемом отключенном состоянии. Системным вызовом connectNamedPipe владелец переводит канал в ждущее состояние. Теперь процессы-клиенты могут подключаться к другому концу канала при помощи файловых системных вызовов openNamedPipe. Канал, открытый хотя бы одним клиентом, считается подключенным, сервер и подключенные клиенты могут работать с ним, используя вызовы файлового API. Клиенты отключаются от канала вызовом close, в распоряжении владельца есть вызов disconnectNamedPipe - разрыва канала. Помимо обычных вызовов файлового обмена, для работы с именованным каналом в составе API могут быть специальные системные вызовы, обеспечивающие выполнение сложных транзакций на именованном канале например: transactNamedPipe - взаимный обмен данными (вывод и ввод) за одну операцию, callNamedPipe - обеспечивает также открытие канала, взаимный обмен, закрытие канала. Кроме того, к именованному каналу или к нескольким именованным каналам может быть подключен семафор, который сбрасывается при изменении состояния канала (заполнен - не заполнен, пуст - не пуст).

9.7. Очереди сообщений

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

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

Как правило, возможности процесса-получателя сообщений не ограничиваются чтением по дисциплине FIFO, ему предоставляется более богатый выбор дисциплин: LIFO, по приоритету, по типам, по идентификаторам отправителя и т.п. В распоряжении владельца имеются также средства определения размера очереди, а возможно, и просмотра очереди - неразрушающего чтения из нее. В распоряжении процесса-отправителя имеется только вызов типа sendMessage - посылки сообщения в очередь. Если при попытке процесса послать сообщение обнаруживается, что очередь заполнена, процесс-отправитель блокируется. Это, впрочем, довольно редкий случай, так как системные ограничения на размер очередей никогда не бывают слишком жесткими. Процесс-получатель блокируется при попытке читать сообщение, когда очередь пуста.

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


Рис.9.2. Размещение сообщений в адресном пространстве ядра


Рис.9.3. Размещение сообщений в адресном пространстве процесса-отправителя

Контрольные вопросы

  1. Почему системные вызовы - скобки критических секций применяются для нитей, но не для процессов?
  2. В чем сходство и в чем различия между сигналами и реальными прерываниями?
  3. Процесс, которому посылается сигнал, как правило, в момент посылки неактивен. Как поступает ОС с сигналом в таком случае?
  4. Опишите различия между именованными и неименованными программными средствами взаимодействия процессов.
  5. Какими внутренними механизмами обеспечивается защита от записи в заполненный программный канал и защита от чтения из пустого программного канала?
  6. Покажите, как представить семафор в виде "переменной взаимного исключения" и "события".
  7. Каким образом используются скрытые семафоры во внутренней реализации механизма очередей?
  8. Покажите, что задачи взаимного исключения и синхронизации могут быть решены при помощи очередей сообщений.
  9. Общие области памяти могут располагаться либо в перекрывающейся части виртуальных адресных пространств процессов, либо в изолированных частях виртуальных адресных пространств. Каким образом реализуется тот и другой метод размещения? Сопоставьте их достоинства и недостатки.

НазадОглавлениеВперед
Индекс