/* Это родительский процесс — делаем минутную паузу. */
sleep(60);
} else {
/* Это дочерний процесс — немедленно завершаем работу. */
exit(0);
}
return 0;
}
Скомпилируйте
этот файл и запустите программу. Пока программа работает, перейдите в другое окно и просмотрите список процессов с помощью следующей команды:
% ps -е -o pid,ppid,stat,cmd
Эта команда отображает идентификатор самого процесса и его предка, а также статус процесса и его командную строку. Обратите внимание на присутствие двух процессов с именем
zombie
. Один из них — предок, другой — потомок. У последнего идентификатор родительского процесса равен идентификатору основного процесса
zombie
, при этом потомок обозначен как <defunct> (несуществующий), а его код состояния равен Z (т.е. zombie — зомби).
Итак, мы хотим узнать, что будет, когда программа
zombie
завершится, не вызвав функцию
wait
. Останется ли процесс-зомби? Нет — выполните команду
ps
и убедитесь в этом: оба процесса
zombie
исчезли. Дело в том, что после завершения программы управление ее дочерними процессами принимает на себя специальный процесс — демон
init
, который всегда работает, имея идентификатор 1 (это первый процесс, запускаемый при загрузке Linux). Демон
init
автоматически удаляет все унаследованные им дочерние процессы-зомби.
3.4.4. Асинхронное удаление дочерних процессов
Если дочерний процесс просто вызывает другую программу с помощью функции
exec
, то в родительском процессе можно сразу же вызвать функцию
wait
и пассивно дожидаться завершения потомка. Но очень часто нужно, чтобы родительский процесс продолжал выполняться одновременно с одним или несколькими своими потомками. Как в этом случае получать сигналы об их завершении?
Один подход заключается в периодическом вызове функции
wait3
или
wait4
. Функция
wait
в данной ситуации не подходит, так как в случае отсутствия завершившегося дочернего процесса она заблокирует основную программу. А вот упомянутые две функции принимают дополнительный флаг
WNOHANG
, переводящий их в неблокируемый режим, в котором функция либо удаляет дочерний процесс, если он есть, либо просто завершается. В первом случае возвращается идентификатор процесса, во втором — 0.
Более элегантный подход состоит в асинхронном уведомлении родительского процесса о завершении потомка. Существуют разные способы сделать это, но проще всего воспользоваться сигналом
SIGCHLD
, посылаемым как раз тогда, когда завершается дочерний процесс. По умолчанию программа никак не реагирует на этот сигнал, поэтому раньше вы могли и не знать о его существовании.
Таким образом, нужно организовать удаление дочерних процессов в обработчике сигнала
SIGCHLD
. Естественно, код состояния удаляемого процесса следует сохранять в глобальной переменной, если эта информация необходима основной программе. В листинге 3.7 показана программа, в которой реализована данная методика.
Листинг 3.7. (sigchld.c) Удаление дочерних процессов в обработчике сигнала
SIGCHLD
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
sig_atomic_t child_exit_status;
void clean_up_child_process(int signal_number) {
/*
Удаление дочернего процесса. */
int status;
wait(&status);
/* Сохраняем статус потомка в глобальной переменной. */
/* Далее выполняются основные действия, включая порождение
дочернего процесса. */
/* ... */
return 0;
}
Глава 4
Потоки
Потоки, как и процессы, — это механизм, позволяющий программам выполнять несколько действий одновременно. Потоки работают параллельно. Ядро Linux планирует их работу асинхронно, прерывая время от времени каждый из них, чтобы дать шанс остальным.
С концептуальной точки зрения поток существует внутри процесса, являясь более мелкой единицей управления программой. При вызове программы Linux создает для нее новый процесс, а в нем — единственный поток, последовательно выполняющий программный код. Этот поток может создавать дополнительные потоки. Все они находятся в одном процессе, выполняя ту же самую программу, но, возможно, в разных ее местах.
Мы уже знаем, как программа порождает дочерний процесс. Первоначально он находится в родительской программе, получая копии ее виртуальной памяти, дескрипторов файлов и т.п. Модификация содержимого памяти, закрытие файлов и другие подобные действия в дочернем процессе не влияют на работу родительского процесса и наоборот. С другой стороны, когда программа создает поток, ничего не копируется. Оба потока — старый и новый — имеют доступ к общему виртуальному пространству, общим дескрипторам файлов и другим системным ресурсам. Если, к примеру, один поток меняет значение переменной, это изменение отражается на другом потоке. Точно так же, когда один поток закрывает файл, второй поток теряет возможность работать с этим файлом. В связи с тем что процесс и все его потоки могут выполнять лишь одну программу одновременно, как только одни из потоков вызывает функцию семейства
exec
, все остальные потоки завершаются (естественно, новая программа может создавать собственные потоки).
В Linux реализована библиотека API-функций работы с потоками, соответствующая стандарту POSIX (она называется Pthreads). Все функции и типы данных библиотеки объявлены в файле
<pthread.h>
. Эти функции не входят в стандартную библиотеку языка С, поэтому при компоновке программы нужно указывать опцию
– lpthread
в командной строке.
4.1. Создание потока
Каждому потоку в процессе назначается собственный идентификатор. При ссылке на идентификаторы потоков в программах, написанных на языке С или C++, нужно использовать тип данных