
- •Раздел 3. Параллельное выполнение программ
- •3.1. Концепция процесса
- •3.2. Средства описания параллелизма
- •3.2.1. Графические средства
- •3.2.3. Описание процессов средствами uml
- •3.2.4. Языковые средства описания параллелизма
- •3.3. Организация ядра ос
- •3.3.1. Ядро как средство организации виртуальной машины
- •3.3.2. Состояния процесса и структура ядра
- •3.3.3. Дескрипторы процессов
- •3.3.4. Очереди процессов в ядре
- •3.4. Общая характеристика примитивов ядра
- •3.5. Примитивы создания и уничтожения процессов
- •3.6. Примитивы синхронизации процессов
- •3.6.1. Простейшие примитивы, не учтенные в классификации
- •3.6.2. Примитивы временной синхронизации
- •3.6.3. Примитивы событийной синхронизации процессов
- •3.7. Общий семафор как средство событийной синхронизации
- •3.8. Средства синхронизации в существующих операционных системах
- •3.9. Монитор как средство реализации взаимного исключения
- •3.10. Примитивы ядра для обмена сообщениями
- •3.10.1. Буфер как средство коммуникации между процессами
- •3.10.2. Почтовый ящик с очередью сообщений
- •3.10.3. Средства коммуникаций в существующих ос
- •3.11. Проблема тупиков при взаимодействии процессов
- •3.12. Планирование загрузки процессора в ядре
- •3.12.1. Классификация алгоритмов планирования
- •3.12.2. Тесты планируемости задач и классификация задач
- •3.12.3. Динамическое планирование
- •3.12.3.1. Планирование независимых задач
- •1. Алгоритм монотонной скорости.
- •2. Алгоритм “задача с минимальным предельным сроком завершения - первая”
- •3. Алгоритм минимальной неопределенности
- •3.12.3.2. Планирование зависимых задач
- •3.12.4. Статическое планирование
3.8. Средства синхронизации в существующих операционных системах
Все ОС в качестве средств синхронизации берут за основу семафоры, рассмотренные выше. Хотя их реализация на каждой платформе может быть различной.
Рассмотрим примеры семафоров в различных средах.
Windows
В Windows существует 4 основных объекта синхронизации - критическая секция, событие, Mutex, семафор.
Для работы с критическими секциями используются следующие функции.
IntializeCriticalSection() - инициализация специальных структур, создаваемых для доступа к критическим секциям.
EnterCriticalSection() - вход в критическую секцию.
LeaveCriticalSection() - выход из критической секции.
DeleteCriticalSection() - удаление структур данных, созданных для доступа к критическим секциям.
Для работы с событиями имеются следующие функции.
CreateEvent() - создание семафора событий.
OpenEvent() - открытие существующего семафора событий.
SetEvent() - сигнализация о наступлении события.
WaitForSingleObject() - ожидание события.
CloseHandle() - закрытие объекта.
Для объекта Mutex существуют следующие операции.
CreateMutex() - создание объекта.
OpenMutex() - получение доступа к ранее созданному объекту.
RequestMutex() - аналог операции P.
ReleaseMutex() - аналог операции V.
CloseHandle() - закрытие объекта.
Семафор - более сложный объект синхронизации. Для работы с ним используются следующие функции.
CreateSemaphore() - создание семафора.
OpenSemaphore() - открытие семафора.
WaitForSingleObject() - ожидание на семафоре.
ReleaseSemaphore() - освобождение семафора.
CloseHandle() - закрытие объекта.
LINUX
Linux также имеет полный набор семафорных операций.
semget() - создание семафора.
semop() - используется как для V-операции, так и для P-операции в зависимости от передаваемых параметров.
semctl() - чтение состояния семафора.
Система Модула-2
В программной системе Модула-2 имеется тип данных SIGNAL, который представляет собой запись, содержащую переменную целого типа и указатель на очередь.
С переменной S типа SIGNAL могут выполняться следующие операции:
WAIT(S) - ждать сигнала, которая аналогична Р-операции;
SEND(S) - послать сигнал, которая аналогична V-операции.
3.9. Монитор как средство реализации взаимного исключения
Логика прикладных процессов, а также виды ресурсов и правил доступа к ним безграничны. Поэтому понятие семафора может быть трансформировано в более обобщенный объект. Этот обобщенный объект называется монитором.
Вопрос (
После того как мы рассмотрим понятие «монитор», мы увидим, что семафор является частным случаем монитора.
)Вопрос
Вопрос (
Монитор представляет собой совокупность данных и процедур работы с этими данными.
Процедуры монитора, которые доступны пользователям монитора - процессам, называются внешними.
)Вопрос
Вопрос (
Монитор по определению представляет собой совокупность данных и процедур работы с этими данными. Причем, в качестве данных монитора могут выступать и очереди процессов.
Процессы не имеют непосредственного доступа к данным, а только через внешние процедуры монитора.
Процедуры монитора содержат операции, позволяющие на основе анализа данных монитора, ставить процессы в очереди и активизировать их.
Алгоритмы анализа данных зависят от содержательной стороны задачи.
Процедуры монитора выполняются в режиме взаимного исключения. Причем, вопрос реализации взаимного исключения – это вторичный вопрос. Вопрос реализации самого монитора тоже является вторичным вопросом.
)Вопрос
Свойства монитора, перечисленные выше, могут быть реализованы как в модуле, так и в объекте. Шаблон модуля, который может рассматриваться как монитор, представлен ниже.
Unit Monitor;
Interface
Procedure M1; {точки входа в монитор}
Procedure M2;
Implementation
Type
Const
... {данные, не доступные пользователю}
Var
...
Procedure DisableInterrupt; {внутренние процедуры монитора}
Begin
asm cli end;
End;
Procedure EnableInterrupt;
Begin
asm sti end;
End;
{внутренних процедур может быть сколько угодно}
Procedure M1; {процедура, связанная с входом в критический}
Begin {участок}
DisableInterrupt;
...
EnableInterrupt;
End;
Procedure M2; {процедура, связанная с выходом из критического}
Begin {участка}
DisableInterrupt;
...
EnableInterrupt;
End;
Begin
{Инициализация данных монитора}
End.
Использование монитора выглядит следующим образом:
Program Example;
Uses Monitor;
Procedure P1;
Begin
While True Do Begin
...
M1;
Критический участок;
M2;
...
End;
End;
Procedure P2;
Begin
While True Do Begin
...
M1;
Критический участок;
M2;
...
End;
End;
Begin
{Инициализация ядра}
End.
Вопрос (
Процедура М1 монитора, анализируя данные, выполняет следующие действия:
проверяет условия входа в критический участок; при выполнении этих условий процесс, вызвавший процедуру М1, продолжает выполняться, войдя в критический участок; при невыполнении - процесс блокируется в очереди монитора;
устанавливает необходимые значения данных монитора при блокировании процесса и при продолжении выполнения в критическом участке.
)Вопрос
Вопрос (
Процедура М2 монитора выполняет следующие действия:
устанавливает необходимые значения данных монитора при выходе из критического участка;
проверяет условия возможной активизации ждущих процессов и активизирует их при выполнении этих условий.
)Вопрос
Количество различающихся процедур входа в критический участок и методов выхода из него определяется количеством разновидностей процессов в каждой прикладной задаче.
Как видно из приведенного описания, семафоры можно рассматривать как монитор с конкретным алгоритмом функционирования.
Реальным примером монитора служат библиотечные модули Модулы-2, которым присвоен приоритет. В Модуле-2 существуют два вида модулей - программные и библиотечные, аналоги Program и Unit в Паскале.
Структура программы на Модуле-2 выглядит следующим образом.
MODULE Name;
IMPORT LIB_MODULE;
...
BEGIN
...
END Name.
Библиотечный модуль имеет следующее описание.
DEFINITION MODULE LIB_MODULE; {модуль определений}
Procedure M1; {аналог части INTERFACE }
Procedure M2;
END LIB_MODULE.
IMPLEMENTATION MODULE LIB_MODULE; {модуль реализации}
{реализация всех процедур модуля} {аналог части IMPLEMENTATION}
END LIB_MODULE.
Есть и другие тонкости модульного принципа построения программ на Модуле-2, в частности - локальные модули, описанные внутри других модулей, но они не имеют прямого отношения к рассматриваемым вопросам.
Важно, что с библиотечным модулем можно связать ПРИОРИТЕТ. Это делается следующим образом.
IMPLEMENTATION MODULE LIB_MODULE[Pr];
За именем модуля в модуле реализации в квадратных скобках может стоять число. Это число определяет приоритет модуля. Интерпретация приоритета зависит от системы.
По правилам Модулы-2, если модуль имеет приоритет, то модуль является монитором и его процедуры выполняются в режиме взаимного исключения, т. е. одновременно может выполняться только одна процедура монитора.
Это достигается включением инструкций запрета и разрешения прерываний на входе и выходе внешних процедур монитора самим компилятором. Это большое достоинство системы Модула-2, что пользователь не манипулирует низкоуровневыми инструкциями, а использует понятие приоритет для построения монитора.
В системе Модула-2 фирмы JPI приоритет модуля трактуется компилятором как маска прерываний. Т. е., если приоритет 1, то маскируется первый вход контроллера прерываний - вход от таймера, если приоритет 2, то маскируются прерывания от клавиатуры, если приоритет 3, то т. к. в двоичном виде это число 11, то маскируются как прерывания от таймера, так и от клавиатуры. Т.е. используется по возможности более тонкий вариант, чем просто запрет прерываний.
Существует большое количество прикладных задач, которые могут быть решены с помощью мониторов. Решение некоторого набора таких задач является предметом лабораторных работ по дисциплине «Системы реального времени».
Указанные задачи относятся к задачам двух типов:
задачи, которые по своей физической природе относятся к программированию параллельных процессов, что соответствует содержанию данной дисциплины;
задачи, которые моделируют параллельными вычислительными процессами некоторые реальные ситуации, что соответствует названию дисциплины "Системы реального времени.
К первому классу задач относятся:
задача назначения однородных ресурсов;
задача с процессами «читателями» и «писателями».
Ко второму классу задач относятся:
модель железнодорожного перегона;
модель дорожного перекрестка;
модель обедающих философов;
модель клиент-сервер (может быть отнесена и к первому классу задач);
модель функционирования лифта;
модель парикмахерской.
Здесь мы рассмотрим задачи первого класса, иллюстрирующие более сложные условия блокировки и активизации процессов, чем в семафорах. Цель рассмотрения - показать, как ставятся подобные задачи, и каковы подходы к их решению.
Задача назначения однородных ресурсов
Задача назначения однородных ресурсов формулируется следующим образом.
Вопрос (
Имеется N единиц ресурса и М процессов.
Каждый из процессов может запросить любое количество R ресурса от 1 до N единиц.
Если нужное количество R ресурса имеется, то оно предоставляется процессу, а количество свободных ресурсов уменьшается на величину R.
Если требуемого количества свободных ресурсов нет в наличие, то процесс блокируется до их появления.
Освобождая ресурсы, процесс возвращает их в число свободных ресурсов и активизирует процессы, которые стоят в очереди и которым хватает свободных ресурсов.
Считается, что все процессы равноправны.
)Вопрос
Посмотрим, как может выглядеть монитор в этом случае. Опишем его в Паскале-подобной объектно-ориентированной структуре.
Вопрос (
Type
PMonitor = ^TMonitor;
TMonitor = Object
N : Integer; {общее количество единиц ресурсов}
Nf : Integer; {текущее количество свободных ресурсов}
List : PList; {очередь, в которой процессы ждут ресурсы}
Constructor Init(AN : Integer);
Destructor Done;
Procedure Request(R : Integer);
Procedure Release;
End {TMonitor};
)Вопрос
Отметим, что для решения данной задачи в дескрипторе процесса следует выделить поле, в котором будет храниться количество запрашиваемых (когда процесс ждет) и используемых (когда процесс получил и выполняется) ресурсов.
Использование монитора внешне выглядит следующим образом:
Var
M : PMonitor;
Begin
M := New(PMonitor, Init(10));
...
Dispose(M, Done);
End.
Procedure Proc1;
Begin
While True Do Begin
...
M^.Request(случайное число от 1 до N);
...
M^.Release;
...
End {While};
End {Proc1};
Раскроем содержание методов монитора.
Constructor TMonitor.Init(AN : Integer);
Begin
N := AN;
Nf := N;
List := New(PList, Init);
End {TMonitor.Init};
Destructor TMonitor.Done;
Begin
Dispose(List, Done)
End {TMonitor.Done};
Procedure TMonitor.Request(R : Integer);
Begin
Запрет_прерываний;
Записать в дескриптор значение R;
If R > Nf Then Begin
List^.Insert(Текущий_процесс);
ПЕРЕНАЗНАЧИТЬ_ПРОЦЕССОР;
End {If};
Nf := Nf - R;
Разрешение_прерываний;
End {TMonitor.Request};
Условие предоставления ресурсов процессу мы записали следующим образом:
если количество свободных ресурсов больше или равно количеству запрашиваемых ресурсов, то процесс захватывает требуемое количество ресурсов и продолжает выполняться;
если количество свободных ресурсов меньше, чем количество запрашиваемых ресурсов, процесс блокируется в очереди монитора.
Вроде бы все логично, но может иметь место, например, следующая ситуация. Предположим, что есть процесс, стоящий в очереди, которому требуется большое количество ресурсов, а свободных ресурсов все время оказывается меньше, чем требуемое. При этом свободные ресурсы все время занимаются и освобождаются процессами, которым требуется малое количество ресурсов.
Это приводит к тому, что процесс, запрашивающий большое количество ресурсов, может оказаться в состоянии «бесконечного ожидания». Это серьезный недостаток алгоритма монитора.
Как устранить этот недостаток?
Вопрос (
Например, усложнить условие блокировки процесса, добавив в него следующее:
If R > Nf ИЛИ Есть процессы в очереди монитора Then Begin
...
End {If};
В этом случае, процессы, требующие малого количества ресурсов, не смогут получать их в обход процесса, требующего большого количества ресурсов.
Хотя процессы, требующие малого количества ресурсов, будут нести непроизводительные потери, т.к. будут ситуации, когда свободных ресурсов достаточно, но они не будут выделяться, т.к. есть процессы в очереди монитора.
)Вопрос
Аналогичные ситуации могут иметь место при освобождении ресурсов.
Запишем вариант метода TMonitor.Release.
Procedure TMonitor.Release;
Begin
Запрет_прерываний;
Nf := Nf + R;
Перевести в очередь готовых все процессы, для которых сумма
требуемых ресурсов меньше или равна сумме свободных
ресурсов, не зависимо от их места в очереди;
Разрешение_прерываний;
End {TMonitor.Release};
В этом случае, если в начале очереди находится процесс, ждущий большого количества ресурсов, он также может не активизироваться бесконечно долго.
Чтобы устранить указанный недостаток, можно правило активизации процессов записать следующим образом:
While очередь не пустая Do Begin
If первому в очереди процессу хватит свободных ресурсов,
Then
перевести этот процесс в очередь готовых процессов
Else
Break
End {While};
Т.е. активизацию в любом случае следует начинать с первого процесса.
Данный пример показывает характер проблем, возникающих при доступе процессов к ресурсам, и способов их устранения. Важной проблемой является опасность оказаться в режиме бесконечного ожидания некоторыми процессами в очередях монитора. Бесконечное ожидание устраняется использованием подходящих алгоритмов активизации, может быть даже путем отказа от лежащих на поверхности преимуществ.
Задача с процессами «читателями» и «писателями»
Отличие данной задачи от предыдущей состоит в том, что здесь есть два типа процессов с разными правами доступа к ресурсу.
Есть некоторый ресурс, например, файл. Есть несколько процессов-читателей, которые могут только читать содержимое файла, и есть несколько процессов-писателей, которые могут модифицировать содержимое файла.
Вопрос (
В любой момент с файлом может работать или один писатель или любое количество читателей.
)Вопрос
По количеству типов процессов - два, монитор имеет две процедуры доступа к файлу и две процедуры освобождения файла.
Вопрос (
Type
PMOnitor = ^TMonitor;
TMonitor = Object
Nr : Integer; {количество читателей, работающих с файлом}
Nw : Integer; {количество писателей, работающих с файлом}
RList : PList; { Список читателей, ждущих чтения файла }
WList : PList; { Список писателей, ждущих редактирования файла }
Constructor Init;
Destructor Done;
Procedure Enter_R;
Procedure Enter_W;
Procedure Exit_R;
Procedure Exit_W;
End {TMonitor};
)Вопрос
Инициализация данного монитора аналогична инициализации предыдущего монитора.
Procedure Reader; Procedure Writer;
Begin Begin
While True Do Begin While True Do Begin
... ...
M^.Enter_R; M^.Enter_W;
... ...
M^.Exit_R; M^.Exit_W;
... ...
End {While}; End {While};
End {Reader}; End {Writer};
Требуется формализовать условия доступа к файлу читателя и писателя и правила активизации процессов при освобождении файла.
Условие доступа к файлу писателя:
Если файл занят писателем или читателями, то блокировка;
Данное условие формализуется следующим образом:
If Nw > 0 OR Nr > 0 Then ... .
Условие доступа к файлу читателя:
Если файл занят писателем, то {читателей может быть несколько} блокировка;
Данное условие формализуется следующим образом:
If Nw > 0 Then ... .
Предположим, что читатели захватили файл. Добавление других читателей возможно, если файл захвачен читателями. Поэтому появляется опасность, что писатель бесконечно долго будет ждать доступа к файлу, если к нему будут все время обращаться читатели.
Ограничить доступ читателей к файлу можно, усложнив условие доступа:
Если файл занят писателем ИЛИ есть писатели в очереди, то блокировка;
Выходы из критического участка для читателя и писателя выглядят следующим образом.
Читатель:
Если нет читателей, работающих с файлом, И есть писатель в очереди, то активизация писателя;
Писатель:
Как быть писателю, если есть и писатели, и читатели в очередях. Интуитивно кажется, что у писателя должен быть приоритет перед читателями. Поэтому писатель должен активизировать писателя же. Однако в этом случае появляется опасность бесконечного ожидания читателей. Поэтому лучше, если писатель будет активизировать читателей, а если их нет в очереди, то можно активизировать писателя.
Возможен вариант реализации монитора с одной очередью. Если первым в очереди стоит писатель, то активизируется он. Если читатель, то активизируются все читатели до первого писателя. В дескрипторах должна быть раскраска процессов на читателей и писателей.
Этот пример также иллюстрирует возможности попадания процессов в режим бесконечного ожидания при неудачном выборе алгоритмов входа в критический участок и выхода из него.