Э. Таненбаум - Архитектура компьютера (1127755), страница 128
Текст из файла (страница 128)
Если параметр равен 99, это значит, что мы достигли конца буфера, поэтому возвращается О. Если какой-то из процессов не может продолжаться, желательно иметь способ временно его приостановить. Зная об этой потребности, разработчики первой версии ~ача включили в класс Тйгеаб специальные методы зцзреп4 (приостановка) и гезояе (возобновление работы).
Они используются в листинге 6.1. А теперь рассмотрим сам код производителя и потребителя. Сначала производитель генерирует новое число (оператор Р1). Обратите внимание на идентификатор е.ИАХ РМ1МЕ. Префикс е здесь указывает, что имеется в виду идентификатор ИАХ Рй1МЕ, определенный в классе ж По той же причине этот префикс нужен для идентификаторов 1п, оо1, ЬФ1ег и пех1, Затем (оператор Р2) производитель проверяет, не меньше ли 1п значения опт. Если да (например, 1п = 62 и ои1 = 63), это означает, что буфер заполнен, и производитель вызывает метод зцзрепф Если буфер не заполнен, туда помещается новое число (оператор РЗ), и значение 1п инкрементнруется (оператор Р4).
Если при проверке в операторе Р5 обнаруживается, что новое значение 1п на 1 больше, чем ои1 (например, 1п = 17, ои1 = 16), это означает, что значения 1п и ои1 перед увеличением 1п были равны. Тогда производитель делает вывод, что буфер был пуст, потребитель не функционировал (был приостановлен) и продолжает простаивать. Поэтому производитель вызывает метод геяае, чтобы заставить потребителя возобновить работу (оператор Р5). После этого производитель начинает формировать следующее число.
Программа потребителя по структуре очень проста. Сначала проверяется, пуст ли буфер (оператор С1). Если пуст, то потребителю ничего не нужно делать, поэтому он отключается. Если буфер не пуст, то потребитель извлекает из него следующее число для печати (оператор С2) и инкрементирует значение оц1 (оператор СЗ). Если после этого значение оо1 становится на 2 единицы больше 1п (оператор С4), это означает, что на предыдущем шаге значение ои1 было на единицу меньше 1п. Так как условием заполнения буфера как раз и является значение 1п = ои1 — 1, это означает, что производитель был приостановлен, и потребитель вызывает для производителя метод гезове.
После этого число выводится на печать (оператор С5), и весь цикл повторяется. К сожалению, программа содержит ошибку (рис. 6.23). Напомним, что наши два процесса работают асинхронно, то есть с разными скоростями, которые, к тому же, могут меняться. Рассмотрим случай, когда в буфере остается только одно число в элементе 21, и 1п = 22, а ои1 = 21 (рис. 6.23, а). Производитель в операторе Р1 ищет число, а потребитель в операторе С5 печатает число с позиции 20. Виртуальные команды для параллельной работы 513 Потребитель заканчивает печатать число, совершает проверку в операторе Ст и извлекает последнее число из буфера в операторе С2. Затем он инкрементирует значение опт.. В этот момент и 10, и 000 равны 22.
Потребитель печатает число, а затем переходит к оператору С1, в котором он вызывает значения тп и 000 из памяти, чтобы сравнить их (рис. 6.23, б). Производитель в операторе Рб запускает потребителя с оператора С1 100 Производитель в операторе Р1 Потребитель в операторе Сб 100 Производитель в операторе Р1 Потребитель в операторе Сб 100 !и =23 оц1 = 22 !и = 22 оо1= 21 !и = оот = 22 Рис. 6.23. Ситуация, при которой механизм взаимодействия производителя и потребителя не срабатывает В этот момент, после того, как потребитель вызвал значения тп и 001, но еще до того, как он сравнил их, производитель генерирует следующее число.
Он помещает это число в буфер в операторе РЗ и инкрементирует 10 в операторе Р4. Теперь 1п = 23, а ооб = 22. В операторе Р5 производитель обнаруживает, что 1п на единицу больше оо~, а это означает, что в буфере находится одно число. Исходя из этого, производитель делает неверный вывод о том, что потребитель приостановлен, и вызывает для его запуска метод гезове, как показано на рис. 6.23, в. На самом деле потребитель все это время продолжает работать, поэтому вызов метода геяае оказывается лишним. Затем производитель начинает формировать следующее число. В этот момент потребитель продолжает работать.
Он успевает вызвать значения 1п и сит из памяти раньше, чем производитель поместил последнее число в буфер. Так как тп = 22 и 001 = 22, потребитель приостанавливается. К этому моменту производитель генерирует следующее число. Он проверяет указатели и обнаруживает, что тп = 24, а оцб = 22. Из этого он делает заключение, что в буфере находится 2 числа (что соответствует действительности), а потребитель функционирует (что неверно). Производитель продолжает цикл. В конце концов, он заполняет буфер и приостанавливается. После этого оба процесса остаются приостановленными до скончания веков. 514 Глава 6.
Уровень операционной системы Слохсность здесь в том, что между моментом, когда потребитель вызывает 1п и оса, и моментом, когда он отключается, производитель, обнаружив, что зп = = оа1 + 1 и предположив, что потребитель приостановлен (хотя на самом деле он продолжает функционировать), вызывает метод гезсве, чего делать не нужно, поскольку потребитель и так функционирует. Такая ситуация называется состоянием гонок, поскольку успех метода зависит от того, кто после инкремента оса выиграет гонку по проверке 1п и овс. Проблема гонок хорошо известна.
Она настолько серьезна, что через несколько лет после создания 1ача компания Япп переработала класс Тйгеаб, изъяв из него вызовы методов зазрела и геаове, поскольку они очень часто приводили к состоянию гонок. Предложенное решение заключается в изменении языка, но поскольку наша цель — операционные системы, мы обсудим другое решение, которое используется во многих операционных системах, в том числе ПХ1Х и ЪУ1поочъ ХР.
Синхронизация процесса с использованием семафоров Проблему гонок можно разрешить по крайней мере двумя способами. Первый способ — снабдить каждый процесс специальным битом ожидания пробуждения. Этот бит устанавливается, если сигнал запуска получает процесс, который уже запущен.
Если процесс приостанавливается в тот момент, когда этот бит установлен, процесс немедленно перезапускается, а бит сбрасывается. То есть бит ожидания пробуждения как бы сохраняет сигнал запуска для будущего использования. Однако этот метод решает проблему гонок только для двух процессов. В общем случае при наличии и процессов он не работает. Конечно, каждому процессу можно приписать п — 1 таких битов ожидания пробуждения, но это неудобно. В 1561 предложено другое решение этой проблемы. Где-то в памяти находятся две переменные, которые могут содержать неотрицательные целые числа. Эти переменные называются семафорами.
Операционная система предоставляет два системных вызова, ир и асио, которые оперируют семафорами. Вызов ир прибавляет 1 к значению семафора, а вызов доил вычитает 1 из значения семафора. Если операция боип совершается для семафора, значение которого больше О, значение этого семафора уменьшается на 1, и процесс продолжается. Если же значение семафора равно О, то операция бсип не может завершиться. Тогда данный процесс приостанавливается до тех пор, пока другой процесс не выполнит для этого семафора операцию ир.
Как правило, приостановленные процессы собираются в одну очередь, что упрощает задачу их отслеживания. Команда ир проверяет, не равно ли О значение семафора. Если равно, а другой процесс приостановлен, значение семафора увеличивается на 1. После этого приостановленный процесс должен завершить операцию доил, установив в О значение семафора. В этом случае оба процесса могут продолжать работу. Если значение семафора не равно О, команда ир просто увеличивает его на 1. Семафор позволяет сохранять сигналы пробуждения, так что они не пропадут зря. У семафорных команд есть одно важное свойство: если один из процессов начал Виртуальные команды для параллельной работы 515 выполнять команду для семафора, то другой процесс не сможет получить доступ к этому семафору до тех пор, пока первый процесс не завершит выполнение команды или не будет приостановлен, пытаясь выполнить команду бсип при нулевом значении семафора.