34.7.4. Осиротевшие группы процессов (и новый взгляд на сигнал SIGHUP)
В разделе 26.2 вы узнали, что «осиротевшим» считается процесс, который после завершения своего родителя «удочеряется» программой init (с идентификатором процесса 1). Внутри программы процесс-«сироту» можно создать с помощью следующего кода:
if (fork()!= 0) /* Выходим, если это родитель (или в случае ошибки) */
exit(EXIT_SUCCESS);
Представьте, что этот код является частью программы, запущенной из командной оболочки. На рис. 34.3 показано состояние процессов до и после завершения родителя.
После завершения родителя дочерний процесс не просто становится «сиротой», но и входит в
Лидер сессии по определению находится в «осиротевшей» группе процессов. Это вытекает из того факта, что вызов setsid() создает новую группу в новой сессии, отличной от той, в которой размещен родитель лидера.
Рис. 34.3.
Чтобы понять, почему «осиротевшие» группы процессов имеют большое значение, необходимо взглянуть на вещи с точки зрения управления заданиями. Рассмотрим следующую ситуацию, основанную на рис. 34.3.
1. Перед завершением родительского процесса потомок был остановлен (возможно, родитель послал ему соответствующий сигнал).
2. Когда родительский процесс завершается, командная оболочка удаляет его группу из своего списка заданий. Потомок удочеряется процессом init и становится фоновым по отношению к терминалу. Группа процессов, содержащая потомка, становится «сиротой».
3. На данном этапе не существует процесса, который отслеживает с помощью вызова wait() состояние остановленного потомка.
Поскольку командная оболочка не создавала дочерний процесс, она не знает о его существовании и о том, что он входит в ту же группу, что и его завершившийся родитель. Более того, процесс init обратит внимание на потомка только после того, как тот завершится (чтобы освободить процесс-«зомби»). Следовательно, остановленный потомок может находиться в этом состоянии вечно, так как ни один другой процесс не знает, что его работу нужно возобновить с помощью сигнала SIGCONT.
Но даже если остановленный процесс в «осиротевшей» группе имеет выполняющегося родителя, этот родитель не всегда будет иметь возможность послать ему сигнал, если он находится в другой сессии. Он сможет послать сигнал SIGCONT любому другому процессу в одной сессии, но если сессия потомка не совпадает с его собственной, в силу вступают обычные правила для отправки сигналов (см. раздел 20.5); это означает, что родитель не сможет послать сигнал потомку, если тот является привилегированным процессом, изменившим свои учетные данные.
Чтобы избежать ситуаций, подобных вышеописанной, стандарт SUSv3 гласит, что если группа процессов становится осиротевшей и содержит остановленных участников, всем входящим в нее процессам отправляется два сигнала: сначала SIGHUP, который уведомляет участников о том, что они были отключены от своей сессии, и затем SIGCONT, который возобновляет их выполнение. Если осиротевшая группа процессов не имеет остановленных участников, сигналы не отправляются.
Группа процессов может стать «сиротой» по двум причинам: либо потому, что завершился последний родитель в той же сессии, но в другой группе, либо в результате завершения последнего процесса, который имел родителя из другой группы (вторая ситуация проиллюстрирована на рис. 34.3). Но какой бы ни была причина, к «осиротевшей» группе процессов, содержащей остановленных потомков, применяется одна и та же процедура.
Отправка сигналов SIGHUP и SIGCONT осиротевшей группе процессов, некоторые участники которой остановлены, делается для того, чтобы закрыть определенную лазейку в системе управления заданиями. Нет ничего, что бы помешало процессу (с подходящими привилегиями) отправить сигнал остановки участникам уже «осиротевшей» группы. В этом случае процесс останавливается до тех пор, пока какой-то другой процесс (опять же с подходящими привилегиями) не отправит ему SIGCONT.
Функция tcsetpgrp() (см. раздел 34.5), если она вызвана участником осиротевшей группы процессов, возвращает ошибку ENOTTY; функции tcsetattr(), tcflush(), tcflow(), tcsendbreak() и tcdrain() (все описаны в главе 58) в этой же ситуации завершаются ошибкой EIO.
Пример программы