Why sigprocmask is used to block SIGCHLD from delivering in the following code

q0987 picture q0987 · Jul 20, 2011 · Viewed 8.3k times · Source

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.

Answer

Nemo picture Nemo · Jul 20, 2011

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.)