- •Раздел 4. Взаимодействие процессов
- •4.1. Примитивы синхронизации процессов
- •4.1.1. Простейшие примитивы, не учтенные в классификации
- •4.1.2. Примитивы временной синхронизации
- •4.1.3. Примитивы событийной синхронизации процессов
- •4.2. Общий семафор как средство событийной синхронизации
- •4.3. Примитивы ядра для обмена сообщениями
- •Примитивы синхронизации процессов;
- •4.3.1. Буфер как средство коммуникации между процессами
- •4.4. Условные переменные
- •4.5. Монитор как средство реализации взаимного исключения
- •4.6. Проблема тупиков при взаимодействии процессов
- •4.7. Планирование загрузки процессора в ядре
- •4.7.1. Классификация алгоритмов планирования
- •4.7.2. Классификация задач с точки зрения планируемости
- •4.7.3. Динамическое планирование
- •4.7.3.1. Планирование независимых задач
- •1. Алгоритм монотонной скорости.
- •2. Алгоритм “задача с минимальным предельным сроком завершения - первая”
- •3. Алгоритм минимальной неопределенности
- •4.7.3.2. Планирование зависимых задач
- •4.7.4. Статическое планирование
- •Раздел 4
4.1.3. Примитивы событийной синхронизации процессов
Мы уже знаем, что существуют следующие понятия:
Критический ресурс - разделяемый ресурс, с которым могут работать несколько параллельных процессов;
Критический участок - участок программы процесса, в котором описаны действия с критическим ресурсом;
Режим взаимного исключения - режим выполнения критического участка без передачи управления от одного процесса к другому.
Важно, что проблема доступа к критическому ресурсу связана не с числом процессоров, на которых выполняются процессы, а именно с общим ресурсом.
Пример: N - Счет в центральном банке. С двух терминалов делается попытка его изменить.
Ожидаем результат: N = N + S1 + S2;
Можем получить: N = N + S1; или N = N + S2.
Общая идея решения этой проблемы состоит в том, что только один процесс должен работать в критическом участке, а все остальные, подойдя к своим критическим участкам, должны ЖДАТЬ, когда ресурс освободится. Т. е., если один процесс вошел в критический участок, то никакой другой процесс не может в него войти. Когда процесс, который работал в критическом участке, выйдет из него, может входить в свой критический участок другой процесс.
Проблема организации такой очередности вхождения процессов в критические участки является ключевой проблемой параллельного программирования.
Вход в критический участок и выход из него рассматриваются как события. Поэтому средства решения этой проблемы и называются средствами событийной синхронизации.
Существуют аппаратные и программные способы решения этой проблемы в зависимости от логического уровня, на котором рассматривается эта проблема.
Дадим их краткий обзор.
Запрет прерываний
Маскирование прерываний только от таймера
Использование флага занятости ресурса
Первое средство, которое напрашивается само собой - запрет прерываний на входе критического участка и разрешение прерываний на выходе из него.
P1: P2:
cli cli
Критический участок; Критический участок;
sti sti
Этот способ имеет следующие недостатки:
Закрываются все прерывания; система становится «слепой» и «глухой» для всех внешних воздействий на время выполнения критического участка; поэтому этот способ может быть использован только для коротких критических участков, и он так и используется; но это - базовое средство, поэтому оно в том или ином виде присутствует во всех остальных средствах.
Приостанавливается выполнение всех процессов, кроме, естественно, активного процесса, даже тех, которые и не собираются использовать критический ресурс.
Второй способ. Запрет не всех прерываний, а маскирование прерываний только от таймера. Это более тонкий метод. Он устраняет первый из недостатков первого способа организации взаимного исключения, состоящий в том, что система перестает реагировать на любые внешние сигналы. Но второй недостаток, состоящий в том, что процессы, не имеющие отношения к данному критическому ресурсу, тоже приостанавливаются, так и не устранен.
Третий способ. Устанавливается флаг занятости ресурса.
Например:
Flag = 0 - ресурс свободен;
Flag = 1 - ресурс занят.
Процесс перед входом в критический участок проверяет значение флага. Если флаг равен нулю, то процесс устанавливает флаг в состояние 1 (занят) и входит в критический участок. Если флаг равен единице, то процесс снова переходит на проверку значения флага.
При выходе из критического участка процесс устанавливает флаг в состояние 0 (свободен).
Вариант без метки:
M: if (Flag == 1) goto M; while (Flag == 1) ;
Flag = 1;
Критический участок;
Flag = 0;
Оказывается, что этот способ не снимает проблемы. «Откомпилируем» предыдущий код:
M: cmp Flag,1
jz M
mov Flag, 1
Критический участок;
mov Flag, 0
Предположим, что Flag = 0 и передача управления происходит сразу же за инструкцией
cmp Flag,1
и второй процесс выполняет инструкцию cmp Flag,1. Для обоих процессов Flag окажется равным нулю и они оба войдут в критический участок.
Это происходит потому, что Flag сам является критическим ресурсом и доступ к нему надо организовывать в режиме взаимного исключения:
M1 : cli или M1 : sti
cmp Flag,1 cli
jnz M2 cmp Flag,1
sti jz M1
jmp M1 mov Flag,1
M2 : mov Flag,1 sti
sti Критический участок;
Критический участок; mov Flag,0
mov Flag,0
Третий вариант с инструкцией «обменять» - xchg:
mov ax,1
M: xchg ax,Flag
Cmp ax,1
jz M
Критический участок;
mov Flag,0
Если Flag = 0, то Flag установится в 1, а ax установится в 0, и процесс войдет в критический участок.
Если Flag = 1, то он останется в 1 и ax останется в 1, и процесс перейдет на метку М, т.е. будет ждать установки Flag в 0.
Чтение и установка флага производятся за одну инструкцию, а это - неделимое действие по определению.
Отметим, что здесь начинают фигурировать очень низкоуровневые, практически, аппаратные средства. К таким аппаратным средствам относится блокировка магистрали, которой можно обеспечивать неделимость некоторых инструкций. Блокировка магистрали при выполнении инструкции XCHG осуществляется по умолчанию.
Вводя в программе префикс LOCK, можно обеспечить блокировку магистрали при выполнении некоторых других инструкций: INC, DEC, ADD, SUB, AND, OR.
Загрузка TSS осуществляется при блокировке магистрали.
Выводы по третьему способу обеспечения взаимного исключения
Способ имеет достоинство, состоящее в том, что критический участок с флагом имеет маленькую и фиксированную длину.
Способ иллюстрирует важную идею обеспечения взаимного исключения, состоящую в том, что для доступа к одному – основному ресурсу необходимо захватить другой - дополнительный ресурс.
Способ не избавляет от «активного ожидания» – цикла опроса флага до его освобождения.
Способ, как и два предыдущих иллюстрирует общую структуру участка программы, работающего с критическим ресурсом – это наличие логических скобок - вход в критический участок и выход из критического участка:
Вход;
Критический участок;
Выход;
Стремление устранить последний недостаток привело к появлению других, более высокоуровневых средств, которые мы и будем считать примитивами ядра для событийной синхронизации процессов.
Семафоры как примитивы ядра и средство синхронизации
Общая идея всех последующих способов состоит в том, что если процессу приходится ждать освобождения ресурса, то пусть он ждет этого в специальной очереди.
Так появились двоичный и общий семафор Дейкстры.
Двоичный семафор содержит флаг, который свидетельствует о занятости или незанятости ресурса, и очередь, в которой находится процесс, если ресурс занят. При инициализации флаг находится в состоянии «свободен», а очередь пуста.
Над семафором выполняются две операции, соответствующие входу в критический участок и выходу из него.
Вход:
Запрет_прерываний;
if (Flag == Занят) {
Встать в очередь;
ПЕРЕНАЗНАЧИТЬ_ПРОЦЕССОР;
}
Flag = Занят;
Разрешение_прерываний;
Выход:
Запрет_прерываний;
Flag = Свободен;
if (Очередь не пуста) {
Перевести первый процесс из этой очереди
в очередь готовых процессов;
ПЕРЕНАЗНАЧИТЬ_ПРОЦЕССОР;
}
Разрешение_прерываний;
Особенности семафора:
Вставая в очередь семафора до освобождения ресурса, процесс разгружает процессор;
На каждый ресурс нужен свой семафор, а значит, своя очередь, поэтому в ядре и много очередей;
Семафор - это тоже разделяемый ресурс, а значит, действия с ним должны выполняться в режиме взаимного исключения; более того, если рассматривать семафор как примитив ядра, вместо операции Запрет_прерываний необходимо ставить операции ПРОЛОГ, КОНТРОЛЬ.
Использование семафора обеспечивает возможность безостановочной работы процессов, не требующих ресурсов.