Base on http://man7.org/tlpi/code/online/book/procexec/multi_SIGCHLD.c.html
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); /* Disable buffering of stdout */
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");
/* Block SIGCHLD to prevent its delivery if a child terminates
before the parent commences the sigsuspend() loop below */
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: /* Child - sleeps and then exits */
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: /* Parent - loops to create next child */
break;
}
}
/* Parent comes here: wait for SIGCHLD until all children are dead */
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);
}
Here is my understanding:
sigprocmask(SIG_SETMASK, &blockMask, NULL)
The resulting signal set of calling process shall be the set pointed to by blockMask.
Question
Why do we say the following statement?
Block SIGCHLD to prevent its delivery if a child terminates before the parent commences the sigsuspend() loop below
In other words, I don't understand why sigprocmask is used to block SIGCHLD based on the given description of the sigprocmask statement.
Well, I think the comment is pretty clear...
Whenever you call fork() and want to interact with the child in any way, there are race conditions to consider. What if the child runs for a while before the parent does? Or vice-versa?
In this case, there is no way to know how long the parent will take, following the call to fork
, to reach the call to sigsuspend
. So what if the forked child finishes its sleep
and calls exit
before the parent calls sigsuspend
? Then the parent will receive a SIGCHLD
, which it is ignoring... And then it will call sigsuspend
, which will never return because the SIGCHLD
has already been delivered.
The only 100% solution is to block SIGCHLD
before calling fork
, and then atomically unblock it on entrance to sigsuspend
. (Handling this sort of race condition is exactly why sigsuspend
needs a signal mask as argument... If you tried to unblock the signal before calling sigsuspend
, there would be a race condition; i.e. the signal might be delivered before you started waiting. Changing the signal mask and then entering the wait must be atomic, and you must block any signal you want to wait for before it can possibly be generated.)