1

While reading up and learning about signals, I found a program, that uses signals in a specific way. I tried understand it, but I am not sure, how all the parts of the code interact with another.

Below is the above mentioned code and I added comments, where I have difficulties:

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define CP 5

static volatile int curprocs =0; ; 

static void die() {
    exit(EXIT_FAILURE);
}


static void chldhandler() {
    int e = errno;
    // Why do we use waitpid here? What does it do?
    while(waitpid(-1, NULL, WNOHANG) > 0) {
        curprocs--;
    }
    errno = e;
}


void do_work() {
    time_t t;
    srand((unsigned) time(&t));
    sleep(5+ rand() % 4); 
}

int main() {
    struct sigaction sa = {
        .sa_handler = chldhandler,
        .sa_flags = SA_RESTART,
    };

    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
    exit(-1);
    }

    while(1) {
    sigset_t chld, empty;
    sigemptyset(&empty);
    sigemptyset(&chld);
    sigaddset(&chld, SIGCHLD);  

    // What do the following lines of code do??
    sigprocmask(SIG_BLOCK, &chld, NULL); 
    while (curprocs >= CP) { // cap for the number of child processes 
        sigsuspend(&empty); 
    }
    curprocs++; 
    sigprocmask(SIG_UNBLOCK, &chld, NULL);

    pid_t p = fork();
    if (p == -1) { 
        return -1;
    }
    if (p == 0) {
        // code for the child processes to execute
        do_work(); 
        die();
    } else {
    // Parent process does nothing 
    }
}
return 0; 
}

Obviously above program is intended to have a max amount of 42 child processes doing work. Whenever we want to have a new child process, we use fork, and adjust curprocs. Whenever a child process finishes, chldhandler() is called and curprocs is adjusted as well.

However I don't understand the interplay of the two sigproc_mask, sigsuspend, waitpid and our two signalsets chld, empty.

Can someone explain what these lines do or why they are used the way they are?

Imago
  • 521
  • 6
  • 29
  • 1
    The comment sign is `//`, not `\\\`. Did you compile this? – Paul Ogilvie Feb 03 '19 at 12:22
  • Odds are, it creates a race condition with its use of `curprocs`. The `++` and `--` operators are not atomic. – Andrew Henle Feb 03 '19 at 13:23
  • the posted code does not compile!. Amongst many other problems, it is missing the needed `#include` statements for the needed header files. When compiling, always enable the warnings, then fix those warnings. ( for `gcc`, at a minimum use: `-Wall -Wextra -Wconversion -pedantic -std=gnu11` ) Note: other compilers use different options to produce the same results\ – user3629249 Feb 04 '19 at 01:09
  • OT: for ease of readability and understanding: 1) consistently indent the code. Indent after every opening brace '{'. Unindent before every closing brace '}'. Suggest each indent level be 4 spaces. 2) separate code blocks: `for` `if` `else` `while` `do...while` `switch` `case` `default` via a single blank line. 3) separate functions by 2 or 3 blank lines (be consistent) – user3629249 Feb 04 '19 at 01:13
  • OT: regarding: `while (curprocs >= 42)` The posted code contains some 'magic' numbers. 'magic' numbers are numbers with no basis. I.E. 42. 'magic' numbers make the code much more difficult to understand, debug, etc. Suggest using a `enum` statement or `#define` statement(s) to give those 'magic' numbers meaningful names, then use those meaningful names throughout the code. – user3629249 Feb 04 '19 at 01:16
  • Please post a [mcve] so we can reproduce the problem and help you to debug it. – user3629249 Feb 04 '19 at 01:20

1 Answers1

2

sigprocmask(SIG_BLOCK, &chld, NULL); blocks SIGCHLD so that you can be sure that while you do while (curprocs >= 42) the SIGCHLD handler won't interrupt the code, changing curprocs in the middle of the check.

sigsuspends atomically unblocks it and waits for a SIGCHLD (any signal really, since your passing an empty mask), atomically reblocking it when it returns.

The waitpid(-1,/*...*/) in the handler reaps the status of any (that's what the -1 means) child that has a status change (typically termination notification) pending so that the data the kernel associates with the status change can be freed. The second argument would be where the status change info would go but since you passed NULL, the info will simply be dropped. WNOHANG means don't wait if there aren't any more status change notifications.

Since the handler is run in response to SIGCHLD, there should be at least one status change notification, but there could be more because SIGCHLDs can coalesce (it's also possible there isn't any — if you called waitpid from normal context while SIGCHLD was blocked). That's why the handler loops. The WNOHANG is important because without it, after all the status change notifications have been reaped, the waitpid call would block your process until a new notification arrived.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142