Ожидание завершения процесса

После создания дочернего процесса и родительский и дочерний процессы выполняются и диспетчируются независимо. В некоторых ситуациях необходимо, чтобы родительский процесс ожидал завершения дочернего процесса. Для этого используются функции семейства wait.

Функция wait.

pid_t wait (int *status)

Блокирует вызывающий процесс до тех пор, пока не завершится один из его дочерних процессов. Если к моменту выдачи wait дочерний процесс уже завершен, то wait возвращает управление немедленно. Если *status не равен NULL, то в переменную status записывается код завершения процесса. wait возвращает PID завершившегося процесса или -1 если дочерние процессы отсутствуют (в последнем случае устанавливает переменную errno).

Задание 2.

2.1. В программе pr2.c основной процесс создает три дочерних процесса и ожидает их завершения. Выполните программу и запишите выходные данные. Нарисуйте приблизительную временную диаграмму работы всех четырех процессов.

/* pr2.c */

#include <stdio.h>

#include <unistd.h>

int main()

{

int i, pid, status, w

for (i=0; i<3; ++i)

{

pid = fork();

if (pid ==0)

exit(getpid() % 256);

}

while ((w = wait(&status))&& w!= -1)

printf ("Child %x returns status %xn", w, status);

return 0;

}

2.2. Замените exit на kill(SIGKILL, pid_дочернего_процесса). Поясните полученные в п. 2.1 и 2.2 коды возврата дочернего процесса.

Состояние "зомби". Когда процесс завершается, информация о нем сохраняется в системных таблицах для того, чтобы родительский процесс мог получить информацию о нем, в частности, его код возврата при помощи функции wait. Если же родительский процесс не вызывает wait, то дочерний процесс продолжает существовать в так называемом состоянии "зомби". Наличие таких процессов нежелательно, т.к. они потребляют системные ресурсы. Если родительский процесс завершится, так и не вызвав wait, то процесс-потомок удаляется из памяти процессом init (с PID=1).

Задание 3. Выполните программу pr3.c, где основной процесс создает дочерний процесс, завершающийся немедленно. Затем основной процесс "засыпает" на 30 секунд. Запустите программу, и пока она выполняется, из другого окна введите команду

% ps -al

Выпишите и поясните информацию о состоянии обоих процессов. Повторите эту команду после завершения работы программы. Объясните результаты.

/* pr3.c */

#include <stdlib.h>

#include <sys/types.h>

#include <unistd.h>

int main ()

{

pid_t child_pid;

/* Create a child process. */

child_pid = fork ();

if (child_pid > 0) sleep (30);

else exit (0);

return 0;

}

Функция waitpid.

pid_t waitpid (pid_t pid,int *status, int options)

Блокирует вызывающий процесс до тех пор, пока не завершится дочерний процесс с PID, заданным в первом параметре. Если pid=-1, то ожидает завершения любого дочернего процесса (как wait). Если задать в options значение WNOHANG, то waitpid работает в неблокирующем режиме (асинхронно): если нет завершившихся дочерних процессов, то управление немедленно возвращается в вызывающий процесс, при этом waitpid возвращает 0 и устанавливает errno.

Сигнал SIGCHLD. Другой способ получения асинхронного уведомления о завершении дочернего процесса - обработать сигнал SIGCHLD, который ОС посылает родительскому процессу при завершении его потомка.

Задание 4. Напишите программу pr4.c: в основном процессе напишите функцию-обработчик для сигнала SIGCHLD, которая при помощи функции wait получает код возврата дочернего процесса функцией и очищает структуры данных, занимаемые этим процессом; записывает код возврата дочернего процесса в некоторую глобальную переменную. Основной процесс создает дочерний процесс (который завершается немедленно и возвращает какой-нибудь код возврата, например, 2), затем основной процесс в цикле выполняет некоторую работу (например, печатает символ на экране, без буферизации!) и проверяет значение глобальной переменной. После завершения дочернего процесса распечатывает его код возврата. Используйте следующий код для функции-обработчика сигнала и для задания реакции на сигнал SIGCHLD:

#include <signal.h>

#include <string.h>

#include <sys/types.h>

#include <sys/wait.h>

sig_atomic_t exstatus;

void clean_up_child_process (int snumber)

{

int status;

wait (&status);

/* Store its exit status in a global variable. */

exstatus = status;

}

int main ()

{

struct sigaction sigact;

memset (&sigact, 0, sizeof (sigact));

sigact.sa_handler = &clean_up_child_process;

sigaction (SIGCHLD, &sigact, NULL);

...

return 0;

}

Пояснения.

1) В начале функции main функция sigaction задает действие для сигнала: первый параметр - номер сигнала; второй параметр - указатель на структуру sigaction. Поле sa_handler этой структуры может содержать одно из трех значений:

- SIG_DEL - обрабатывать указанный сигнал по умолчанию;

- SIG_IGN - игнорировать указанный сигнал;

- указатель на функцию-обработчик сигнала, которая принимает номер сигнала и возвращает void.

2) Обработчик сигнала полностью удаляет дочерний процесс из памяти функцией waitи записывает код возврата в глобальную переменную. Так как обработчик может быть прерван в любом месте, например, поступлением другого сигнала, то присвоение значения глобальной переменной должно быть неделимой (атомарной) операцией. Это обеспечивается использованием для глобальной переменной типа sig_atomic_t, описанного в <sys/types.h>.