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

1

7

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.

Numbers answered 20/7, 2011 at 4:57 Comment(1)
The sigCnt variable does not mean what that code claims it does...Fleecy
B
10

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

Brindabrindell answered 20/7, 2011 at 5:7 Comment(2)
But why even bother with the sigsuspend at all? what's the rationale for it? why can't the parent just deal with the SIGCHLD signalls as they come, rather than ignoring them and then dealing with them in one go with sigsuspend ?Dextrorse
@horseyguy: Because doing anything nontrivial in an asynchronous signal handler is a recipe for subtle and hard-to-reproduce bugs. If all you want to do is wait for the child, OK... But if you have anything complex to do, keeping the flow control synchronous using sigsuspend will make the code more robust (and easier to audit for robustness).Brindabrindell

© 2022 - 2024 — McMap. All rights reserved.