Архитектура операционной системы UNIX
Шрифт:
Рисунок 7.12. Программа, демонстрирующая возникновение соперничества между процессами в ходе обработки сигналов
В программе описывается именно такое поведение процессов, поскольку вызов родительским процессом функции nice приводит к тому, что ядро будет чаще запускать на выполнение порожденный процесс.
По словам Ричи (эти сведения были получены в
Второе несоответствие в обработке сигналов связано с приемом сигналов, поступающих во время исполнения системной функции, когда процесс приостановлен с допускающим прерывания приоритетом. Сигнал побуждает процесс выйти из приостанова (с помощью longjump), вернуться в режим задачи и вызвать функцию обработки сигнала. Когда функция обработки сигнала завершает работу, происходит то, что процесс выходит из системной функции с ошибкой, сообщающей о прерывании ее выполнения. Узнав об ошибке, пользователь запускает системную функцию повторно, однако более удобно было бы, если бы это действие автоматически выполнялось ядром, как в системе BSD.
Третье несоответствие проявляется в том случае, когда процесс игнорирует поступивший сигнал. Если сигнал поступает в то время, когда процесс находится в состоянии приостанова с допускающим прерывания приоритетом, процесс возобновляется, но не выполняет longjump. Другими словами, ядро узнает о том, что процесс проигнорировал поступивший сигнал только после возобновления его выполнения. Логичнее было бы оставить процесс в состоянии приостанова. Однако, в момент посылки сигнала к пространству процесса, в котором ядро хранит адрес функции обработки сигнала, может отсутствовать доступ. Эта проблема может быть решена путем запоминания адреса функции обработки сигнала в записи таблицы процессов, обращаясь к которой, ядро получало бы возможность решать вопрос о необходимости возобновления процесса по получении сигнала. С другой стороны, процесс может немедленно вернуться в состояние приостанова (по алгоритму sleep), если обнаружит, что в его возобновлении не было необходимости. Однако, пользовательские процессы не имеют возможности осознавать собственное возобновление, поскольку ядро располагает точку входа в алгоритм sleep внутри цикла с условием продолжения (см. главу 2), переводя процесс вновь в состояние приостанова, если ожидаемое процессом событие в действительности не имело места.
Ко всему сказанному выше следует добавить, что ядро обрабатывает сигналы типа «гибель потомка» не так, как другие сигналы. В частности, когда процесс узнает о получении сигнала «гибель потомка», он выключает индикацию сигнала в соответствующем поле записи таблицы процессов и по умолчанию действует так, словно никакого сигнала и не поступало. Назначение сигнала «гибель потомка» состоит в возобновлении выполнения процесса, приостановленного с допускающим прерывания приоритетом. Если процесс принимает такой сигнал, он, как и во всех остальных случаях, запускает функцию обработки сигнала. Действия, предпринимаемые ядром в том случае, когда процесс игнорирует поступивший сигнал
этого типа, будут описаны в разделе 7.4. Наконец, когда процесс вызвал функцию signal с параметром «гибель потомка» (death of child), ядро посылает ему соответствующий сигнал, если он имеет потомков, прекративших существование. В разделе 7.4 на этом моменте мы остановимся более подробно.7.2.2 Группы процессов
Несмотря на то, что в системе UNIX процессы идентифицируются уникальным кодом (PID), системе иногда приходится использовать для идентификации процессов номер «группы», в которую они входят. Например, процессы, имеющие общего предка в лице регистрационного shell'а, взаимосвязаны, и поэтому когда пользователь нажимает клавиши «delete» или «break», или когда терминальная линия «зависает», все эти процессы получают соответствующие сигналы. Ядро использует код группы процессов для идентификации группы взаимосвязанных процессов, которые при наступлении определенных событий должны получать общий сигнал. Код группы запоминается в таблице процессов; процессы из одной группы имеют один и тот же код группы.
Для того, чтобы присвоить коду группы процессов начальное значение, приравняв его коду идентификации процесса, следует воспользоваться системной функцией setpgrp. Синтаксис вызова функции:
grp = setpgrp;
где grp — новый код группы процессов. При выполнении функции fork процесс-потомок наследует код группы своего родителя. Использование функции setpgrp при назначении для процесса операторского терминала имеет важные особенности, на которые стоит обратить внимание (см. раздел 10.3.5).
7.2.3 Посылка сигналов процессами
Для посылки сигналов процессы используют системную функцию kill. Синтаксис вызова функции:
kill(pid, signum)
где в pid указывается адресат посылаемого сигнала (область действия сигнала), а в signum — номер посылаемого сигнала. Связь между значением pid и совокупностью выполняющихся процессов следующая:
• Если pid — положительное целое число, ядро посылает сигнал процессу с идентификатором pid.
• Если значение pid равно 0, сигнал посылается всем процессам, входящим в одну группу с процессом, вызвавшим функцию kill.
• Если значение pid равно -1, сигнал посылается всем процессам, у которых реальный код идентификации пользователя совпадает с тем, под которым исполняется процесс, вызвавший функцию kill (об этих кодах более подробно см. в разделе 7.6). Если процесс, пославший сигнал, исполняется под кодом идентификации суперпользователя, сигнал рассылается всем процессам, кроме процессов с идентификаторами 0 и 1.
• Если pid — отрицательное целое число, но не -1, сигнал посылается всем процессам, входящим в группу с номером, равным абсолютному значению pid.
Во всех случаях, если процесс, пославший сигнал, исполняется под кодом идентификации пользователя, не являющегося суперпользователем, или если коды идентификации пользователя (реальный и исполнительный) у этого процесса не совпадают с соответствующими кодами процесса, принимающего сигнал, kill завершается неудачно.
В программе, приведенной на Рисунке 7.13, главный процесс сбрасывает установленное ранее значение номера группы и порождает 10 новых процессов. При рождении каждый процесс-потомок наследует номер группы процессов своего родителя, однако, процессы, созданные в нечетных итерациях цикла, сбрасывают это значение. Системные функции getpid и getpgrp возвращают значения кода идентификации выполняемого процесса и номера группы, в которую он входит, а функция pause приостанавливает выполнение процесса до момента получения сигнала. В конечном итоге родительский процесс запускает функцию kill и посылает сигнал о прерывании всем процессам, входящим в одну с ним группу. Ядро посылает сигнал пяти «четным» процессам, не сбросившим унаследованное значение номера группы, при этом пять «нечетных» процессов продолжают свое выполнение.