Формирование процесса
Готовая программа подготовлена к выполнению, она должна запускаться как часть какого-либо процесса. Этот процесс, как и все другие процессы, характеризуется состоянием и адресным пространством, через которое можно получить доступ к программам и данным. Информация о состоянии включает как минимум счетчик команд, слово состояния программы, указатель стека и регистры общего назначения.
Большинство современных операционных систем позволяют формировать и прерывать процессы динамически. Для формирования нового процесса требуется системный вызов. Этот системный вызов может просто создать клон вызывающей программы или позволить исходному процессу задать начальное состояние нового процесса, включая его программу, данные и начальный адрес.
В одних случаях исходный (родительский) процесс может сохранять частичный или даже полный контроль над порожденным (дочерним) процессом. Виртуальные команды позволяют родительскому процессу останавливать и снова запускать, проверять и завершать дочерние процессы. В других случаях исходный процесс никак не контролирует дочерний процесс: после того как новый процесс сформирован, исходный процесс не может его остановить, запустить заново, проверить или завершить. Таким образом, эти два процесса работают независимо друг от друга.
Состояние гонок
Во многих случаях параллельные процессы должны взаимодействовать, тогда их нужно синхронизировать. В этом подразделе мы рассмотрим синхронизацию и некоторые связанные с этим проблемы. Способы разрешения этих проблем будут представлены в следующем разделе.
Рассмотрим два независимых процесса, процесс 1 и процесс 2, которые взаимодействуют через общий буфер в основной памяти. Для простоты будем называть процесс 1 производителем (producer), а процесс 2 — потребителем (consumer). Производитель генерирует простые числа и помещает их в буфер по одному. Потребитель по одному извлекает их из буфера и печатает.
Эти два процесса работают параллельно с разной скоростью. Если производитель обнаруживает, что буфер заполнен, он переходит в режим ожидания, то есть временно приостанавливает операцию и ожидает сигнала от потребителя. Когда потребитель удаляет число из буфера, он посылает сигнал производителю, чтобы тот возобновил работу. Если потребитель обнаруживает, что буфер пуст, он приостанавливает работу. Когда производитель помещает число в пустой буфер, он посылает соответствующий сигнал потребителю.
В нашем примере для взаимодействия процессов мы задействуем кольцевой буфер. Указатели in и out используются следующим образом: in указывает на следующее свободное слово (куда производитель сможет поместить очередное число), a out — на следующее число, которое должен извлечь потребитель. Если in = out, буфер пуст, как показано на рис. 6.22, а. На рис. 6.22, б показана ситуация после того, как производитель сгенерировал несколько чисел. На рис. 6.22, в изображен буфер после того, как потребитель извлек из него несколько чисел для печати. На рис. 6.22, г-е представлены промежуточные этапы работы буфера. Буфер заполняется циклически. Если в буфер отправлено слишком много чисел, и он начинает заполняться по второму кругу (снизу), а под адресом out есть только одно свободное слово (например, in = 52, a out = 53), буфер заполнится. Последнее слово не используется; в противном случае не было бы возможности сообщить, что именно значит равенство in = out, полный буфер или пустой.