Эта команда отображает идентификатор самого процесса и его предка, а также статус процесса и его командную строку. Обратите внимание на присутствие двух процессов с именем zombie
. Один из них — предок, другой — потомок. У последнего идентификатор родительского процесса равен идентификатору основного процесса zombie
, при этом потомок обозначен как
Итак, мы хотим узнать, что будет, когда программа zombie
завершится, не вызвав функцию wait()
. Останется ли процесс-зомби? Нет — выполните команду ps
и убедитесь в этом: оба процесса zombie
исчезли. Дело в том, что после завершения программы управление ее дочерними процессами принимает на себя специальный процесс — демон init
, который всегда работает, имея идентификатор 1 (это первый процесс, запускаемый при загрузке Linux). Демон init
автоматически удаляет все унаследованные им дочерние процессы-зомби.
3.4.4. Асинхронное удаление дочерних процессов
Если дочерний процесс просто вызывает другую программу с помощью функции exec()
, то в родительском процессе можно сразу же вызвать функцию wait()
и пассивно дожидаться завершения потомка. Но очень часто нужно, чтобы родительский процесс продолжал выполняться одновременно с одним или несколькими своими потомками. Как в этом случае получать сигналы об их завершении?
Один подход заключается в периодическом вызове функции wait3()
или wait4()
. Функция wait()
в данной ситуации не подходит, так как в случае отсутствия завершившегося дочернего процесса она заблокирует основную программу. А вот упомянутые две функции принимают дополнительный флаг WNOHANG
, переводящий их в неблокируемый режим, в котором функция либо удаляет дочерний процесс, если он есть, либо просто завершается. В первом случае возвращается идентификатор процесса, во втором — 0.
Более элегантный подход состоит в асинхронном уведомлении родительского процесса о завершении потомка. Существуют разные способы сделать это, но проще всего воспользоваться сигналом SIGCHLD
, посылаемым как раз тогда, когда завершается дочерний процесс. По умолчанию программа никак не реагирует на этот сигнал, поэтому раньше вы могли и не знать о его существовании.
Таким образом, нужно организовать удаление дочерних процессов в обработчике сигнала SIGCHLD
. Естественно, код состояния удаляемого процесса следует сохранять в глобальной переменной, если эта информация необходима основной программе. В листинге 3.7 показана программа, в которой реализована данная методика.
SIGCHLD
#include
#include
#include
#include
sig_atomic_t child_exit_status;
void clean_up_child_process(int signal_number) {
/* Удаление дочернего процесса. */
int status;
wait(&status);
/* Сохраняем статус потомка в глобальной переменной. */
child_exit_status = status;
}
int main() {
/* Обрабатываем сигнал SIGCHLD, вызывая функцию
clean_up_child_process(). */
struct sigaction sigchld_action;
memset(&sigchld_action, 0, sizeof(sigchld_action));
sigchld_action.sa_handler = &clean_up_child_process;
sigaction(SIGCHLD, &sigchld_action, NULL);
/* Далее выполняются основные действия, включая порождение
дочернего процесса. */
/* ... */
return 0;
}
Глава 4
Потоки
Потоки, как и процессы, — это механизм, позволяющий программам выполнять несколько действий одновременно. Потоки работают параллельно. Ядро Linux планирует их работу асинхронно, прерывая время от времени каждый из них, чтобы дать шанс остальным.
С концептуальной точки зрения поток существует внутри процесса, являясь более мелкой единицей управления программой. При вызове программы Linux создает для нее новый процесс, а в нем — единственный поток, последовательно выполняющий программный код. Этот поток может создавать дополнительные потоки. Все они находятся в одном процессе, выполняя ту же самую программу, но, возможно, в разных ее местах.