Sahil
Sahil

Reputation: 9496

How will the SIGCHILD signal be processed in this scenario?

I am trying to understand this piece of code,

int count =0;
void handler(int sig){
    count++;
}

int main(){
    signal(SIGCHILD,handler);
    for(int i=0;i<4;i++){
        if(!fork()){
            exit(0 );
        }
    }

    while(wait(NULL) ! = -1){

    }

    print(count)
}

So, I expect that since there are 4 SIGCHILD signals, handler is expected to be called four times. However, since we can have maximum one pending signal, some signals may be discarded and count might not be four.

However, if parent process gets to calling wait before a single child has exited, how will the SIGCHILD signal be processed? Will the count be 4 in that case?

How will the flow between SIGCHILD, handler code and wait of parent process be in this case?

Upvotes: 0

Views: 523

Answers (1)

Jonathan Leffler
Jonathan Leffler

Reputation: 755010

A somewhat cleaned-up version of your code becomes an MCVE (Minimal, Complete, Verifiable Example):

#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

static volatile sig_atomic_t count = 0;

static void handler(int sig)
{
    assert(sig == SIGCHLD);
    count++;
}

int main(void)
{
    signal(SIGCHLD, handler);
    for (int i = 0; i < 4; i++)
    {
        if (fork() == 0)
        {
            exit(16 * (i + 1));
        }
    }

    int corpse;
    int status;
    while ((corpse = wait(&status)) != -1 || errno == EINTR)
    {
        if (corpse == -1 && errno == EINTR)
            printf("Interrupted by a signal\n");
        else
            printf("Child %d exited with status 0x%.4X\n", corpse, status);
    }

    printf("Count = %d\n", count);
    return 0;
}

When run on a Mac running macOS Sierra 10.12.4 with GCC 6.3.0, I got variations on the theme of:

Child 74003 exited with status 0x1000
Child 74004 exited with status 0x2000
Child 74005 exited with status 0x3000
Child 74006 exited with status 0x4000
Count = 4

On this machine (a modern 15" 2016 MacBook Pro), I always seem to get that as the output — process IDs for the children in sequence, with the carefully tailored exit statuses.

When I changed the handler like this (bearing in mind the restrictions of how to avoid calling printf() in a signal handler — and yes, I know I could type STDIN_FILENO instead of some of those 1s):

static void handler(int sig)
{
    assert(sig == SIGCHLD);
    count++;
    char s[2] = { count + '0', '\n' };
    write(1, "SH: count = ", sizeof("SH: count = ")-1);
    write(1, s, 2);
}

then the output changed to something more like this:

SH: count = 1
SH: count = 2
Child 74113 exited with status 0x1000
Child 74114 exited with status 0x2000
SH: count = 3
Child 74115 exited with status 0x3000
SH: count = 4
Child 74116 exited with status 0x4000
Count = 4

This shows that the signal handler was called at various times during the loop. The BSD signal handlers (and macOS or Darwin is somewhat based on BSD) tend to restart the system call rather than interrupting. What I see, therefore, is not necessarily what you would see on a different platform.

For example, running in an Ubuntu 16.04 LTS VM, I got the output:

SH: count = 1
Child 13310 exited with status 0x4000
Child 13309 exited with status 0x3000
Child 13308 exited with status 0x2000
Child 13307 exited with status 0x1000
Count = 1

However, with another adaptation of the signal handler — to reset the signal handler in the signal handler, since the traditional (non-BSD) behaviour for handlers set by signal() is that the default is reset before the handler function is called, and checking the number of bytes written since Linux has warnings in place if you ignore the return value from write():

static void handler(int sig)
{
    //assert(sig == SIGCHLD);
    signal(sig, handler);
    count++;
    char s[2] = { count + '0', '\n' };
    int nb = write(1, "SH: count = ", sizeof("SH: count = ")-1);
    assert(nb == sizeof("SH: count = ")-1);
    nb = write(1, s, 2);
    assert(nb == 2);
}

Then the output became:

SH: count = 1
Child 13838 exited with status 0x4000
SH: count = 2
Child 13837 exited with status 0x3000
SH: count = 3
Child 13836 exited with status 0x2000
SH: count = 4
Child 13835 exited with status 0x1000
Count = 4

So, as you can see, the results you see depend on the platform where you run the code, and on exactly how the handler() function is written.

Upvotes: 1

Related Questions