- •4.10 Аппаратная реализация взаимоисключения:
- •Testandset (a, b)
- •4.11 Семафоры
- •4.12 Синхронизация процессов при помощи семафоров
- •4.13 Пара "производитель-потребитель"
- •4.14 Считающие семафоры
- •5.2 Мониторы
- •5.3 Простое распределение ресурсов при помощи мониторов
- •5.4 Пример монитора: кольцевой буфер
- •5.5 Пример монитора: читатели и писатели
5.3 Простое распределение ресурсов при помощи мониторов
Предположим, что нескольким процессам необходим доступ к определенному ресурсу, который может быть использован в каждый конкретный момент времени только одним процессом. На рис. 5.1 приведен простой монитор-распределитель ресурсов, обеспечивающий выделение и освобождение подобного ресурса;
monitor распределительресурсов;
var ресурсзанят: логический;
ресурссвободен: условие;
procedure захватитьресурс;
begin
if ресурсзанят then
wait(pecypccвoбoдeн);
ресурсзанят := истина
end;
procedure возвратитьресурс;
begin
ресурсзанят := ложь;
signal(ресурссвободен)
end;
begin
ресурсзанят := ложь
end;
Рис. 5.1 Простое распределене ресурса при помощи монитора.
Достоинство подобного распределителя ресурсов заключается в том, что он работает точно так же, как двоичный семафор: команда "захватитьресурс" выполняет функцию Р-оператора, а команда "возвратитьресурс" – V-оператора. Поскольку с помощью такого простого монитора с одним ресурсом можно реализовать семафор, мониторы по меньшей мере обладают той же логической мощностью, что и семафоры.
5.4 Пример монитора: кольцевой буфер
В настоящее время операционные системы строятся, как правило, в виде множества асинхронных параллельных процессов, работающих под управлением ядра. Эти процессы служат для организации параллельных работ, выполняются достаточно независимо, однако требуют периодического взаимодействия. В этой теме мы рассмотрим так называемый кольцевой буфер в т.ч., каким образом он может применяться в случаях, когда процесс-производитель должен передать данные процессу-потребителю.
Производителю иногда требуется передать данные, в то время как потребитель еще не готов их принять, а потребитель иногда пытается принять данные, которые производитель еще не выдал. Поэтому необходимо иметь соответствующие средства синхронизации работы процесса-производителя и процесса-потребителя.
В операционных системах часто предусматривается выделение некоторого фиксированного количества ячеек памяти для использования в качестве буфера передач между процессом-производителем и процессом-потребителем. Этот буфер можно представить в виде массива заданного размера. Процесс-производитель помещает передаваемые данные в последовательные элементы этого массива. Процесс-потребитель считывает данные в том порядке, в котором они помещались. Производитель может опережать потребителя на несколько шагов. Со временем процесс-производитель займет последний элемент массива. Когда он сформирует после этого очередные данные для передачи, он должен будет "возвратиться к исходной точке" и снова начать с записи данных в первый элемент массива (при этом естественно предполагается, что потребитель уже прочитал те данные, которые ранее сюда поместил производитель). Такой массив по сути работает как замкнутое кольцо, поэтому и носит название кольцевого буфера.
Поскольку размер кольцевого буфера ограничен, процесс-производитель в какой-то момент может столкнуться с тем, что все элементы массива окажутся занятыми; в этом случае процессу-производителю необходимо будет подождать, пока потребитель не прочитает и тем самым не освободит хотя бы один элемент массива. Аналогично может возникнуть ситуация, когда потребитель хотел бы прочитать данные, а массив оказывается пустым; в этом случае потребитель должен будет ждать, пока процесс-производитель не поместит данные в массив. На рис. 5.2 приведен монитор под названием "мониторкольцевогобуфера", который реализует кольцевой буфер и соответствующий механизм синхронизации для обеспечения взаимодействия в паре "производитель-потребитель" (базовую версию этого монитора предложил Хоор (Но 74)).
monitor мониторкольцевогобуфера;
var кольцевойбуфер: array [0.. размербуфера – 1] of тип;
текущая позиция: 0.. размербуфера;
очереднаязаполняемаяпозиция: 0.. размербуфера – 1;
очереднаяосвобождаемаяпозиция: 0..размербуфера – 1;
буфернепуст, буфернезаполнен: условие;
procedure заполнитьпозицию(данные: тип);
begin
if текущая позиция = размербуфера
then wait(буфернезаполнен);
кольцевойбуфер [очереднаязаполняемаяпозиция]: = данные;
текущаяпозиция:= текущая позиция + 1;
очереднаязаполняемаяпозиция := (очереднаязаполняемаяпозиция + 1) mod размербуфера;
signal(бyфepнeпycт)
end;
procedure освободитьпозицию(var данные:тип);
begin
if текущаяпозиция = 0 then wait(буфернепуст);
данные := кольцевойбуфер [очереднаяосвобождаемаяпозиция];
текущаяпозиция := текущаяпозиция – 1;
очереднаяосвобождаемаяпозиция := (очереднаяосвобождаемая-
позиция + 1) mod размербуфера;
signal (буфернезаполнен)
end;
begin
текущаяпозиция := 0;
очереднаязаполняемаяпозиция := 0;
очереднаяосвобождаемаяпозиция := 0
end;
Рис. 5.2 Реализация кольцевого буфера при помощи монитора.
Мы будем предполагать, что массив содержит определенное количество позиций (задаваемое константой "размербуфера"), рассчитанных на занесение данных некоторого также задаваемого типа. Переменные "очереднаязаполняемаяпозиция" и "очереднаяосвобождаемаяпозиция" указывают соответственно, в какой сегмент необходимо поместить очередной элемент данных и откуда необходимо будет выбирать очередной элемент данных.
Условие "буфернезаполнен" является признаком, которого ждет процесс-производитель, если обнаружит, что кольцевой буфер целиком заполнен; этот признак устанавливается по сигналу процесса-потребителя о том, что он только что освободил позицию.
Условие "буфернепуст" является признаком, которого ждет процесс-потребитель, если обнаружит, что кольцевой буфер пуст; этот признак устанавливается по сигналу процесса-производителя о том, что он только что поместил данные в некоторую позицию.
Механизм кольцевого буфера весьма удобен для реализации управления спулингом в операционных системах. В качестве одного из наиболее распространенных примеров спулинга можно привести ситуацию, когда процесс формирует строки данных, подлежащие выводу на такое относительно низкоскоростное внешнее устройство, как построчный принтер. Поскольку процесс может формировать строки данных с гораздо более высокой скоростью, чем принтер способен их распечатывать, и поскольку желательно создать для процесса такой режим, при котором он мог бы работать с максимально высокой скоростью, выходные строки данных процесса направляются в кольцевой буфер
Первый процесс, записывающий данные в буфер, обычно называется спулером.
Другой процесс читает строки данных из кольцевого буфера и выдает их на принтер. Однако этот второй процесс, обычно называемый деспулером, работает с гораздо меньшей скоростью – со скоростью принтера.
Кольцевой буфер должен иметь достаточно большой размер, чтобы "выбирать слабину", которая возникает из-за несоответствия скоростей работы спулера и деспулера.