В главе 1 уже говорилось о частом объединении вызовов fork(2) и exec(2), получившем специальное название fork-and-exec. Таким образом загружается подавляющее большинство программ, которые выполняются в системе.
При порождении процесса, который впоследствии может загрузить новую программу, "родителю" может быть небезынтересно узнать о завершении выполнения "потомка". Например, после того как запущена утилита ls(1), командный интерпретатор приостанавливает свое выполнение до завершения работы утилиты и только после этого выдает свое приглашение на экран. Можно привести еще множество ситуаций, когда процессам необходимо синхронизировать свое выполнение с выполнением других процессов. Одним из способов такой синхронизации является обработка родителем сигнала
SIGCHLD
, отправляемого ему при "смерти" потомка. Механизм сигналов мы рассмотрим в следующем разделе. Сейчас же остановимся на другом подходе.
Операционная система предоставляет процессу ряд функций, позволяющих ему контролировать выполнение потомков. Это функции wait(2), waitid(2) и waitpid(2):
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* stat_loc);
int waitpid(idtype_t idtype, id_t id,
siginfo_t * infop, int options);
pid_t waitpid(pid_t pid, int *stat_loc, int options);
Первый из этих вызовов wait(2) обладает самой ограниченной функциональностью — он позволяет заблокировать выполнение процесса, пока кто-либо из его непосредственных потомков не прекратит существование. Вызов wait(2) немедленно возвратит состояние уже завершившегося дочернего процесса в переменной
stat_loc
, если последний находится в состоянии зомби. Значение
stat_loc
может быть проанализировано с помощью следующих макроопределений:
WIFEXITED(status)
| Возвращает истинное (ненулевое) значение, если процесс завершился нормально. |
WEXITSTATUS(status)
| Если WIFEXITED(status) не равно нулю, определяет код возврата завершившегося процесса (аргумент функции exit(2)). |
WIFSIGNALLED(status)
| Возвращает истину, если процесс завершился по сигналу. |
WTERMSIG(status)
| Если WIFSIGNALLED(status) не равно нулю, определяет номер сигнала, вызвавшего завершение выполнения процесса. |
WCOREDUMP(status)
| Если WIFSIGNALLED(status) не равно нулю, макрос возвращает истину в случае создания файла core. |
Системный вызов waitid(2) предоставляет больше возможностей для контроля дочернего процесса. Аргументы
idtype
и
id
определяют, за какими из дочерних процессов требуется следить:
| Значение аргумента idtype | Описание |
P_PID
| waitid(2) блокирует выполнение процесса, следя за потомком, PID которого равен id . |
P_PGID
| waitid(2) блокирует выполнение процесса, следя за потомками, идентификаторы группы которых равны id . |
P_ALL
| waitid(2) блокирует выполнение процесса, следя за всеми непосредственными потомками. |
Аргумент
options
содержит флаги, объединенные логическим ИЛИ, определяющие, за какими изменениями в состоянии потомков следит
waitid(2):
| Флаги аргумента options | Описание |
WEXITED
| Предписывает ожидать завершения выполнения процесса. |
WTRAPPED
| Предписывает ожидать ловушки (trap) или точки останова (breakpoint) для трассируемых процессов. |
WSTOPPED
| Предписывает ожидать останова процесса из-за получения сигнала. |
WCONTINUED
| Предписывает вернуть статус процесса, выполнение которого было продолжено после останова. |
WNOHANG
| Предписывает завершить свое выполнение, если отсутствует статусная информация (т.е. отсутствует ожидаемое событие). |
WNOWAIT
| Предписывает получить статусную информацию, но не уничтожать ее, оставив дочерний процесс в состоянии ожидания. |
Аргумент
infop
указывает на структуру
siginfo_t
, которая будет заполнена информацией о потомке. Мы рассмотрим эту структуру в следующем разделе.
Функция waitpid(2), как и функции wait(2) и waitid(2), позволяет контролировать определенное множество дочерних процессов.
В заключение для иллюстрации описанных в этом разделе системных вызовов приведем схему работы командного интерпретатора при запуске команды.
...
/* Вывести приглашение shell*/
write(1, "$ ", 2);
/* Считать пользовательский ввод */
get_input(inputbuf);
/* Произвести разбор ввода: выделить команду cmd
и ее аргументы arg[] */
parse_input(inputbuf, and, arg);
/* Породить процесс */
pid = fork();
if (pid == 0) {
/* Запустить программу */
execvp(cmd, arg);
/* При нормальном запуске программы эта часть кода
выполняться уже не будет — можно смело выводить
сообщение об ошибке */