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);blocksSIGCHLDso that you can be sure that while you dowhile (curprocs >= 42)theSIGCHLDhandler won't interrupt the code, changingcurprocsin the middle of the check.sigsuspendsatomically 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.WNOHANGmeans 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 becauseSIGCHLDs can coalesce (it's also possible there isn't any — if you calledwaitpidfrom normal context whileSIGCHLDwas blocked). That's why the handler loops. TheWNOHANGis important because without it, after all the status change notifications have been reaped, thewaitpidcall would block your process until a new notification arrived.