Читаем Linux API. Исчерпывающее руководство онлайн бесплатно
16:45:18 handler: Caught SIGCHLD
16:45:18 handler: Reaped child 17767 — child exited, status=0
16:45:19 Child 2 (PID=17768) exiting
16:45:21 Child 3 (PID=17769) exiting
16:45:23 handler: returning
16:45:23 handler: Caught SIGCHLD
16:45:23 handler: Reaped child 17768 — child exited, status=0
16:45:23 handler: Reaped child 17769 — child exited, status=0
16:45:28 handler: returning
16:45:28 All 3 children have terminated; SIGCHLD was caught 2 times
Обратите внимание на вызов sigprocmask() в листинге 26.5 , который блокирует сигнал SIGCHLD до создания потомков. Это делается для того, чтобы обеспечить корректную работу цикла sigsuspend() внутри родителя. Если бы мы этого не сделали, а потомок завершился бы между проверкой значения numLiveChildren и выполнением вызова sigsuspend() (или как вариант вызова pause()), тогда вызов sigsuspend() заблокировался бы навсегда в ожидании сигнала, который уже был перехвачен
. Подробности о том, как решается проблема с этим видом состояния гонки, приводятся в разделе 22.9.
Листинг 26.5. Снятие завершенных дочерних процессов с помощью обработчика SIGCHLD
procexec/multi_SIGCHLD.c
#include
#include
#include "print_wait_status.h"
#include "curr_time.h"
#include "tlpi_hdr.h"
static volatile int numLiveChildren = 0;
/* Количество запущенных потомков, которых еще не дождались */
static void
sigchldHandler(int sig)
{
int status, savedErrno;
pid_t childPid;
/* НЕБЕЗОПАСНО: этот обработчик использует функции printf(), printWaitStatus(),
currTime(), небезопасные в контексте асинхронных сигналов; (подраздел 21.1.2) */
savedErrno = errno; /* На случай, если мы изменим значение 'errno' */
printf("%s handler: Caught SIGCHLD\n", currTime("%T"));
while ((childPid = waitpid(–1, &status, WNOHANG)) > 0) {
printf("%s handler: Reaped child %ld — ",
currTime("%T"), (long) childPid);
printWaitStatus(NULL, status);
numLiveChildren-;
}
if (childPid == –1 && errno!= ECHILD)
errMsg("waitpid");
sleep(5); /* Искусственно увеличиваем время выполнения обработчика */
printf("%s handler: returning\n", currTime("%T"));
errno = savedErrno;
}
int
main(int argc, char *argv[])
{
int j, sigCnt;
sigset_t blockMask, emptyMask;
struct sigaction sa;
if (argc < 2 || strcmp(argv[1], "-help") == 0)
usageErr("%s child-sleep-time…\n", argv[0]);
setbuf(stdout, NULL); /* Отключаем буферизацию стандартного вывода */
sigCnt = 0;
numLiveChildren = argc — 1;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = sigchldHandler;
if (sigaction(SIGCHLD, &sa, NULL) == –1)
errExit("sigaction");
/* Блокируем сигнал SIGCHLD, чтобы предотвратить его доставку в случае, если
потомок завершится до начала цикла sigsuspend() в родителе (см. ниже) */
sigemptyset(&blockMask);
sigaddset(&blockMask, SIGCHLD);
if (sigprocmask(SIG_SETMASK, &blockMask, NULL) == –1)
errExit("sigprocmask");
for (j = 1; j < argc; j++) {
switch (fork()) {
case –1:
errExit("fork");
case 0: /* Потомок приостанавливается, и затем завершается */
sleep(getInt(argv[j], GN_NONNEG, "child-sleep-time"));
printf("%s Child %d (PID=%ld) exiting\n",
currTime("%T"), j, (long) getpid());
_exit(EXIT_SUCCESS);
default: /* Родитель входит в цикл для создания следующего потомка */
break;
}
}
/* Сюда доходит родитель: ждем сигнала SIGCHLD, пока не завершатся все потомки */
sigemptyset(&emptyMask);
while (numLiveChildren > 0) {
if (sigsuspend(&emptyMask) == –1 && errno!= EINTR)
errExit("sigsuspend");
sigCnt++;
}
printf("%s All %d children have terminated; SIGCHLD was caught "
"%d times\n", currTime("%T"), argc — 1, sigCnt);
exit(EXIT_SUCCESS);
}
procexec/multi_SIGCHLD.c
26.3.2. Доставка сигнала SIGCHLD для остановленных потомков
По аналогии с тем, как вызов waitpid() может быть использован для отслеживания остановленных потомков, родитель может получать сигнал SIGCHLD в случае остановки одного из его дочерних процессов (тоже по сигналу). Этого можно добиться с помощью флага SA_NOCLDSTOP, когда вызов sigaction() устанавливает обработчик SIGCHLD. Если этот флаг опущен, сигнал SIGCHLD доставляется родителю при остановке одного из потомков; если флаг присутствует, этого не происходит (реализация вызова signal(), показанная в разделе 22.7, не предусматривает флага SA_NOCLDSTOP).
Поскольку сигнал SIGCHLD по умолчанию игнорируется, флаг SA_NOCLDSTOP имеет значение только в том случае, если установить обработчик SIGCHLD. Более того, SIGCHLD — это единственный сигнал, на который влияет флаг SA_NOCLDSTOP.




