2

I'm trying to understand how blocking and unblocking signals work and I'm trying to understand the following piece of code. Specifically I am looking at line 28 (commented in the code): int a = sigprocmask(SIG_UNBLOCK, &mask, NULL);, aka where the signal is unblocked in the child.

The textbook I got the code from says that the code uses signal blocking in order to ensure that the program performs its add function (simplified to printf("adding %d\n", pid);) before its delete function (simplified to printf("deleting %d\n", pid);). This makes sense to me; by blocking the SIGCHLD signal, then unblocking it after we perform the add function, we ensure that handler isn't called until we perform the add function. However, why would we unblock the signal in the child? Doesn't that just eliminate the whole point of blocking by immediately unblocking it, allowing the child to delete before the parent adds?

However, the output (described after the code) is identical whether or not I have the line commented out or not, meaning that is clearly not what happens. The textbook states:

"Notice that children inherit the blocked set of their parents, so we must be careful to unblock the SIGCHLD signal in the child before calling execve."

But that still seems to me like the unblocking would result in the handler being called. What exactly does this line do?

void handler(int sig) {
    pid_t pid;
    printf("here\n");
    while ((pid = waitpid(-1, NULL, 0)) > 0); /* Reap a zombie child */
    printf("deleting %d\n", pid); /* Delete the child from the job list */
}

int main(int argc, char **argv) {
    int pid;
    sigset_t mask;
    signal(SIGCHLD, handler);
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, NULL); /* Block SIGCHLD */

    pid = fork();
    if (pid == 0) {
        printf("in child\n");

        int a = sigprocmask(SIG_UNBLOCK, &mask, NULL); // LINE 28

        printf("a is %d\n",a);
        execve("/bin/date", argv, NULL);
        exit(0);
    }

    printf("adding %d\n", pid);/* Add the child to the job list */
    sleep(5);
    printf("awake\n");

    int b = sigprocmask(SIG_UNBLOCK, &mask, NULL);
    printf("b is %d\n", b);
    sleep(3);

    exit(0);
}

Outputs:

adding 652

in child

a is 0

Wed Apr 24 20:18:04 UTC 2019

awake

here

deleting -1

b is 0
Jonny Henly
  • 4,023
  • 4
  • 26
  • 43
Evan
  • 193
  • 9
  • 3
    You can't safely call `printf()` in a signal handler. Per [**7.1.4 Use of library functions**, paragraph 4 of the C standard](https://port70.net/~nsz/c/c11/n1570.html#7.1.4p4): *The functions in the standard library are not guaranteed to be reentrant and may modify objects with static or thread storage duration.* and [footnote 188](https://port70.net/~nsz/c/c11/n1570.html#note188): *Thus, a signal handler cannot, in general, call standard library functions.* POSIX allows the calling of "async-signal-safe" functions from a signal handler, and provides a list. `printf()` is not on that list. – Andrew Henle Apr 24 '19 at 20:56
  • 3
    A blocking call to `waitpid()` in a signal handler, such as with `waitpid(-1, NULL, 0)`, is also a bad idea. `while ((pid = waitpid(-1, NULL, WNOHANG)) > 0);` is much better - that will reap all child processes that have exited without hanging indefinitely waiting for any child that hasn't exited. If there are child processes that haven't exited, your signal handler will hang until they all have exited. – Andrew Henle Apr 24 '19 at 21:01
  • 3
    Unblocking in the child has no effect on the parent. – Barmar Apr 24 '19 at 21:04

1 Answers1

1

However, why would we unblock the signal in the child? Doesn't that just eliminate the whole point of blocking by immediately unblocking it, allowing the child to delete before the parent adds?

No. Each process has its own signal mask. A new process inherits its parent's signal mask, but only in the same sense that it inherits the contents of the parent's memory -- the child gets what amounts to an independent copy. Its modifications to that copy are not reflected in the parent's copy, nor vise versa after the child starts. If this were not the case, then all processes in the system would share a single signal mask.

It is only the parent that must not receive SIGCLD too soon, so only the parent needs to have that signal blocked.

[...] The textbook states:

"Notice that children inherit the blocked set of their parents, so we must be careful to unblock the SIGCHLD signal in the child before calling execve."

But that still seems to me like the unblocking would result in the handler being called.

Again, "inherit" in the sense of inheriting a copy, not in the sense of sharing the same mask.

What exactly does this line do?

It unblocks SIGCLD in the child -- again, having no effect on the parent -- in case it being blocked would interfere with the behavior of /bin/date, which the child is about to exec.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157