Жизненные циклы родительского и дочернего процессов обычно не совпадают — один из них живет дольше, чем другой. Из этого вытекает два вопроса.

• Кто становится родителем «осиротевшего» потомка? Ответ: init — предок всех процессов, имеющий идентификатор 1. Иными словами, после того, как родитель потомка завершит работу, вызов getppid() начнет возвращать 1. С помощью этого вызова можно узнать, жив ли еще настоящий родитель потомка (подразумевается, что потомок был создан любым другим процессом, кроме init).

Применение операции PR_SET_PDEATHSIG системного вызова prctl() (доступного только в Linux) позволяет сделать так, чтобы в случае потери родителя процесс получал выбранный сигнал.

• Что происходит с потомком, который завершается до того, как его родитель имел возможность выполнить wait()? Дело в том, что, хоть потомок и закончил свою работу, родителю все равно должно быть позволено сделать вызов wait(), чтобы определить причину завершения. Ядро решает эту проблему путем превращения потомка в «зомби». Это означает, что большинство ресурсов, занимаемых потомком, возвращаются системе и могут быть задействованы другими процессами. Единственная часть дочернего процесса, которая остается нетронутой, — это запись в таблице процессов ядра, хранящая (помимо прочего) идентификатор потомка, код завершения и статистику использования ресурсов (см. раздел 36.1).

Что касается «зомби», системы Unix следуют канонам, принятым в художественных фильмах, — процесс-«зомби» нельзя убить сигналом, даже если это SIGKILL (своеобразная серебряная пуля). Благодаря этому родитель всегда может выполнить вызов wait().

Когда родитель наконец делает вызов wait(), ядро удаляет процесс-«зомби», поскольку информация о нем больше не требуется. С другой стороны, если родитель завершает работу, так и не выполнив вызов wait(), процесс init удочеряет потомка и автоматически делает этот вызов самостоятельно, удаляя таким образом «зомби» из системы.

Если родитель создает потомка, но не успевает вызвать wait(), запись о дочернем процессе-«зомби» все равно продолжит храниться в таблице процессов ядра. Если будет создано слишком большое количество «зомби», они в какой-то момент переполнят эту таблицу, мешая созданию новых процессов. Поскольку «зомби» нельзя убить по сигналу, единственный способ удалить их из системы заключается в принудительном (или штатном) завершении работы их родителя; в этом случае они удочеряются процессом init, который начинает их ожидать, что приводит к их удалению из системы.

Последствия такой семантики имеют большое значение при проектировании долгоживущих родительских процессов, таких как сетевые серверы и командные оболочки, создающих большое количество потомков. Иначе говоря, родительский процесс в таких приложениях должен выполнять вызовы wait(), чтобы гарантировать удаление отработанных потомков из системы и не дать им превратиться в неубиваемых «зомби». Такие вызовы можно делать синхронно или асинхронно в ответ на сигнал SIGCHLD, как описывается в разделе 26.3.1.

В листинге 26.4 показан процесс создания «зомби» и тот факт, что их нельзя принудительно завершить с помощью сигнала SIGKILL. При запуске этой программы мы увидим следующий вывод:

$ ./make_zombie

Parent PID=1013

Child (PID=1014) exiting

1013 pts/4 00:00:00 make_zombie Вывод команды ps(1)

1014 pts/4 00:00:00 make_zombie

After sending SIGKILL to make_zombie (PID=1014):

1013 pts/4 00:00:00 make_zombie Вывод команды ps(1)

1014 pts/4 00:00:00 make_zombie

В выводе, представленном выше, видно, что команда ps(1) выводит строку , обозначающую процесс-«зомби».

Программа из листинга 26.4 использует функцию system() для выполнения консольной команды, передаваемой ей в виде символьно-строкового аргумента. Подробно эта функция описывается в разделе 27.6.

Листинг 26.4. Создание дочерних процессов-«зомби»

procexec/make_zombie.c

#include

#include  /* Для объявления basename() */

#include "tlpi_hdr.h"

#define CMD_SIZE 200

int

main(int argc, char *argv[])

{

char cmd[CMD_SIZE];

pid_t childPid;

setbuf(stdout, NULL); /* Отключаем буферизацию стандартного вывода */

printf("Parent PID=%ld\n", (long) getpid());

switch (childPid = fork()) {

case –1:

errExit("fork");

case 0: /* Потомок немедленно завершается, чтобы стать «зомби» */

printf("Child (PID=%ld) exiting\n", (long) getpid());

_exit(EXIT_SUCCESS);

default: /* Родитель */

sleep(3); /* Даем потомку шанс начать выполнение и завершиться */

snprintf(cmd, CMD_SIZE, "ps | grep %s", basename(argv[0]));

system(cmd); /* Видим потомка-«зомби» */

/* Теперь отправляем «зомби» сигнал о безусловном завершении */

if (kill(childPid, SIGKILL) == –1)

errMsg("kill");

sleep(3); /* Даем потомку шанс отреагировать на сигнал */

printf("After sending SIGKILL to zombie (PID=%ld):\n",

(long) childPid);

system(cmd); /* Опять видим потомка-«зомби» */

exit(EXIT_SUCCESS);

}

}

procexec/make_zombie.c

Перейти на страницу:

Похожие книги