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?
sigprocmask(SIG_BLOCK, &chld, NULL);
blocksSIGCHLD
so that you can be sure that while you dowhile (curprocs >= 42)
theSIGCHLD
handler won't interrupt the code, changingcurprocs
in the middle of the check.sigsuspends
atomically unblocks it and waits for aSIGCHLD
(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 passedNULL
, 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 becauseSIGCHLD
s can coalesce (it's also possible there isn't any — if you calledwaitpid
from normal context whileSIGCHLD
was blocked). That's why the handler loops. TheWNOHANG
is important because without it, after all the status change notifications have been reaped, thewaitpid
call would block your process until a new notification arrived.