Введение

Процесс, который не выполняется, но присутствует в таблице процессов.

В операционных системах Unix и Unix-подобных, зомби-процесс или неактивный процесс – это процесс, который завершил выполнение (через системный вызов exit), но все еще имеет запись в таблице процессов: это процесс в "завершённом состоянии". Это происходит с дочерними процессами, где запись все еще необходима, чтобы родительский процесс мог прочитать статус выхода своего дочернего процесса: как только статус выхода прочитан с помощью системного вызова wait, запись зомби удаляется из таблицы процессов, и процесс считается "пожинаемым". Дочерний процесс сначала становится зомби, и только затем удаляется из таблицы ресурсов. При нормальной работе системы зомби немедленно ожидаются родительским процессом и затем пожинаются системой. Процессы, которые остаются зомби в течение длительного времени, обычно указывают на ошибку и могут приводить к утечке ресурсов. Как правило, единственный ресурс ядра, который они занимают, – это запись в таблице процессов, их идентификатор процесса. Однако зомби могут также удерживать открытые буферы, потребляя память. Зомби могут удерживать дескрипторы файлов, что препятствует выделению пространства для этих файлов файловой системе. Этот эффект можно увидеть по разнице между командами du и df. В то время как du может показывать большое количество свободного дискового пространства, df покажет заполненный раздел. Если зомби не будут удалены, это может заполнить корневой раздел и привести к сбою системы. Термин "зомби-процесс" происходит от общепринятого определения зомби – неживого человека. В метафоре этого термина дочерний процесс "умер", но еще не был "пожинаем". В отличие от обычных процессов, команда kill не оказывает никакого эффекта на зомби-процесс. Зомби-процессы не следует путать с процессами-сиротами, процессами, которые все еще выполняются, но родительский процесс которых завершился. Когда родительский процесс завершается, сиротский дочерний процесс переходит под управление init. Когда процесс-сирота завершается, он не остается зомби-процессом, а ожидается init.

Обзор

Когда процесс завершается выходом, вся память и ресурсы, связанные с ним, освобождаются для использования другими процессами. Однако запись процесса в таблице процессов сохраняется. Родительский процесс может прочитать статус выхода дочернего процесса, выполнив системный вызов `wait`, после чего зомби-процесс удаляется. Вызов `wait` может быть выполнен в последовательном коде, но чаще всего он выполняется в обработчике сигнала `SIGCHLD`, который родительский процесс получает каждый раз, когда завершается дочерний процесс. После удаления зомби его идентификатор процесса (PID) и запись в таблице процессов могут быть повторно использованы. Однако, если родительский процесс не выполнит вызов `wait`, зомби-процесс останется в таблице процессов, что приведет к утечке ресурсов. В некоторых ситуациях это может быть намеренно – родительский процесс хочет продолжать удерживать этот ресурс, например, если он создает другой дочерний процесс, чтобы гарантировать, что ему не будет выделен тот же PID. На современных UNIX-подобных системах (соответствующих спецификации SUSv3 в этом отношении) применяется следующий особый случай: если родительский процесс явно игнорирует сигнал `SIGCHLD`, установив его обработчик в `SIG_IGN` (а не просто игнорируя сигнал по умолчанию) или установив флаг `SA_NOCLDWAIT`, вся информация о статусе выхода дочернего процесса будет отброшена, и зомби-процессы не останутся. Зомби-процессы можно идентифицировать в выводе команды `ps` Unix по наличию символа "Z" в столбце "STAT". Зомби-процессы, существующие в течение длительного времени, обычно указывают на ошибку в родительской программе или на необычное решение не "собирать" дочерние процессы (см. пример). Если родительский процесс больше не работает, зомби-процессы обычно указывают на ошибку в операционной системе. Как и в случае с другими утечками ресурсов, наличие нескольких зомби-процессов само по себе не является критичным, но может указывать на проблему, которая станет серьезной при увеличении нагрузки. Поскольку зомби-процессам не выделяется память (используется только память для записи в таблице процессов), основная проблема при большом количестве зомби-процессов заключается не в нехватке памяти, а в исчерпании записей в таблице процессов, а именно – идентификаторов процессов. Однако зомби-процессы могут удерживать открытыми буферы, связанные с файловыми дескрипторами, тем самым потребляя память. Зомби-процессы также могут удерживать файловый дескриптор удаленного файла, что препятствует восстановлению i-узлов файловой системой. Следовательно, команда для отображения использования диска не будет учитывать удаленные файлы, пространство которых не может быть повторно использовано из-за удерживаемого зомби-процессом файлового дескриптора. Чтобы удалить зомби-процессы из системы, сигнал `SIGCHLD` можно отправить родительскому процессу вручную с помощью команды `kill`. Если родительский процесс по-прежнему отказывается "собирать" зомби-процесс, и если допустимо завершить родительский процесс, следующим шагом может быть его удаление. Когда процесс теряет родительский процесс, `init` становится его новым родителем. `init` периодически выполняет системный вызов `wait` для "сбора" любых зомби-процессов, у которых `init` является родителем.

Пояснение

В первом цикле исходный (родительский) процесс порождает 10 копий самого себя. Каждый из этих дочерних процессов (определяемый тем, что `fork` возвращает ноль) выводит сообщение, засыпает и завершается. Все дети создаются практически одновременно (поскольку родительский процесс выполняет очень мало действий в цикле), поэтому время, когда каждый из них получит процессорное время впервые, несколько случайно, что и приводит к перемешанному порядку их сообщений. В течение цикла создается массив идентификаторов дочерних процессов. Копия массива `pids[]` присутствует во всех 11 процессах, но полная она только в родительском процессе – в копии каждого дочернего процесса будут отсутствовать идентификаторы дочерних процессов с меньшими номерами, а вместо собственного идентификатора будет ноль. (Впрочем, это не имеет большого значения, поскольку этот массив фактически использует только родительский процесс.) Второй цикл выполняется только в родительском процессе (поскольку все дочерние процессы уже завершили работу к этому моменту) и ожидает завершения каждого дочернего процесса. Он ожидает завершения дочернего процесса, который засыпал на 10 секунд; все остальные уже давно завершились, поэтому все сообщения (кроме первого) появляются быстро друг за другом. Здесь нет возможности случайного порядка, поскольку всем управляет цикл в одном процессе. Первое сообщение от родительского процесса фактически появилось раньше любых сообщений от дочерних процессов – родительский процесс смог перейти ко второму циклу до того, как какой-либо из дочерних процессов начал выполняться. Это опять же просто случайное поведение планировщика процессов – сообщение "parent9" могло появиться в любом месте последовательности до "parent8". Дочерние процессы с 0 по 8 проводят одну или несколько секунд в состоянии между завершением работы и моментом, когда родительский процесс вызывает `waitpid` для них. Родительский процесс уже ожидал завершения дочернего процесса 9 до того, как тот завершился, поэтому этот процесс практически не находился в состоянии зомби.