Стандарт SUSv3 также позволяет отправлять родителю сигнал SIGCHLD, если один из его остановленных потомков возобновил работу (получив сигнал SIGCONT) — по аналогии с тем, как действует флаг WCONTINUED в вызове waitpid(). Эта возможность доступна в ядре Linux, начиная с версии 2.6.9.
26.3.3. Игнорирование завершенных дочерних процессов
Существует еще один способ работы с завершенными процессами. Явное изменение диспозиции сигнала SIGCHLD на SIG_IGN приводит к тому, что любой потомок, который впоследствии завершает работу, немедленно удаляется из системы, вместо того чтобы превратиться в «зомби». В этом случае последующий вызов wait() (или аналогичный) не может вернуть никаких сведений о завершенном дочернем процессе, поскольку его статус просто сбрасывается.
Стоит отметить, что, хотя диспозицией сигнала SIGCHLD по умолчанию является игнорирование, явная установка диспозиции SIG_IGN приводит к описанному выше поведению, отличному от поведения по умолчанию. SIGCHLD — единственный сигнал, который ведет себя таким образом.
В Linux, как и во многих реализациях UNIX, изменение диспозиции сигнала SIGCHLD на SIG_IGN не влияет на статус уже имеющихся потомков-«зомби», которых по-прежнему нужно ждать. Хотя в некоторых версиях UNIX (таких как Solaris 8) это приводит к удалению существующих дочерних процессов-«зомби».
Семантика SIG_IGN для сигнала SIGCHLD имеет длинную историю, берущую свое начало в System V. Поведение, описанное выше, предусмотрено стандартом SUSv3, однако данная семантика не является частью оригинального стандарта POSIX.1. Таким образом, игнорирование сигнала SIGCHLD в некоторых реализациях UNIX никак не влияет на создание процессов-«зомби». Единственный полностью переносимый способ предотвратить появление зомби заключается в использовании вызовов wait() или waitpid(), возможно даже внутри обработчика, установленного для SIGCHLD.
В стандарте SUSv3 описан флаг SA_NOCLDWAIT вызова sigaction(), с помощью которого можно изменять действие сигнала SIGCHLD. Этот флаг обеспечивает поведение, похожее на то, к которому приводит изменение действия SIGCHLD на SIG_IGN. Он был реализован в Linux 2.6.
Использование флага SA_NOCLDWAIT принципиально отличается от изменения действия SIGCHLD на SIG_IGN тем, что стандарт SUSv3 умалчивает, должен ли сигнал SIGCHLD передаваться родителю при завершении потомка. Иными словами, в отдельной реализации, если указан флаг SA_NOCLDWAIT, сигнал SIGCHLD может доставляться, и приложение может его перехватить (хотя обработчик не сможет получить статус потомка с помощью вызова wait(), поскольку ядро успевает уничтожить процесс-«зомби»). В некоторых реализациях UNIX, в том числе и в Linux, ядро действительно генерирует сигнал SIGCHLD для родителя. Но есть системы, в которых этого не происходит.
Использование вызовов wait() и waitpid() (и других связанных с ними функций) позволяет родительскому процессу получать статус его завершенных и остановленных потомков. Этот статус сигнализирует, завершился ли дочерний процесс в штатном режиме (успешно или нет), завершился ли аварийно, был ли он остановлен или возобновлен по сигналу (во втором случае это сигнал SIGCONT).
Если родитель завершает свою работу, его потомки становятся «сиротами» и «удочеряются» процессом init, чей идентификатор равен 1.
Когда завершается дочерний процесс, он становится «зомби» и удаляется из системы только после того, как его родитель получит его статус с помощью вызова wait() (или аналогичного). Долгоживущие программы, такие как командные оболочки и демоны, нужно проектировать так, чтобы они всегда могли получить статус созданного ими потомка, поскольку процессы в состоянии «зомби» не могут быть завершены, и в какой-то момент они переполнят таблицу процессов ядра.
Стандартный способ снятия завершенных дочерних процессов заключается в установлении обработчика для сигнала SIGCHLD. Этот сигнал доставляется родителю всякий раз, когда один из его потомков завершает работу, и, опционально, когда потомок останавливается по сигналу. Есть и другой, менее переносимый вариант: процесс может изменить диспозицию сигнала SIGCHLD на SIG_IGN, в результате чего статус завершенных потомков сразу же сбрасывается (и больше не может быть получен родителем) и они не становятся «зомби».
Ознакомьтесь с источниками, приведенными в разделе 24.6.
26.1. Напишите программу, в которой проверяется утверждение, что при завершении родительского процесса вызов getppid() в его потомках возвращал 1 (идентификатор процесса init).