user3162878 user3162878 - 1 month ago 9
C Question

sending signal from parent to child

I am using this tutorial from website http://www.code2learn.com/2011/01/signal-program-using-parent-child.html and trying to understand why signal is not recieved by child?

here is the code:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void sighup(); /* routines child will call upon sigtrap */
void sigint();
void sigquit();
void main()
{ int pid;
/* get child process */
if ((pid = fork()) < 0) {
perror("fork");
exit(1);
}
if (pid == 0)
{ /* child */
signal(SIGHUP,sighup); /* set function calls */
signal(SIGINT,sigint);
signal(SIGQUIT, sigquit);
for(;;); /* loop for ever */
}
else /* parent */
{ /* pid hold id of child */
printf("\nPARENT: sending SIGHUP\n\n");
kill(pid,SIGHUP);
sleep(3); /* pause for 3 secs */
printf("\nPARENT: sending SIGINT\n\n");
kill(pid,SIGINT);
sleep(3); /* pause for 3 secs */
printf("\nPARENT: sending SIGQUIT\n\n");
kill(pid,SIGQUIT);
sleep(3);
}
}
void sighup()
{ signal(SIGHUP,sighup); /* reset signal */
printf("CHILD: I have received a SIGHUP\n");
}
void sigint()
{ signal(SIGINT,sigint); /* reset signal */
printf("CHILD: I have received a SIGINT\n");
}
void sigquit()
{ printf("My DADDY has Killed me!!!\n");
exit(0);
}


output:

enter image description here

Answer

It's a race condition. Your code assumes that the child runs first and is not preempted by the parent until it installed all signal handlers and starts looping forever.

When that is not the case, the parent may send a signal to the child before the child had the chance to catch the signal. As such, the child process is killed, since the default action for SIGHUP, SIGINT and SIGQUIT is to terminate.

In your specific case, you never see any output from the child. This means that the parent sent SIGHUP to the child, and SIGHUP was delivered before the child changed the default behavior. So the child was killed.

Actually, if you did some error checking on the returning value of kill(2) - which you should - you would see ESRCH in the parent upon trying to send SIGINT and SIGQUIT, because the child is already gone (assuming no other process in the system was started and got assigned the same PID in the meantime).

So, how do you fix it? Either use some form of synchronization to force the child to run first and only let the parent execute after all signal handlers are installed, or set up the signal handlers before forking, and then unset them in the parent. The code below uses the latter approach:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

void sighup(int); /* routines child will call upon sigtrap */
void sigint(int);
void sigquit(int);

int main(void) {

    int pid;

    signal(SIGHUP,sighup); /* set function calls */
    signal(SIGINT,sigint);
    signal(SIGQUIT, sigquit);

    /* get child process */
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {

        /* child */
        for(;;); /* loop for ever */

    } else {

        signal(SIGHUP, SIG_DFL);
        signal(SIGINT, SIG_DFL);
        signal(SIGQUIT, SIG_DFL);

        /* parent */
        /* pid hold id of child */
        printf("\nPARENT: sending SIGHUP\n\n");
        kill(pid,SIGHUP);
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGINT\n\n");
        kill(pid,SIGINT);
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGQUIT\n\n");
        kill(pid,SIGQUIT);
        sleep(3);
    }

    return 0;
}

void sighup(int signo) {
    signal(SIGHUP,sighup); /* reset signal */
    printf("CHILD: I have received a SIGHUP\n");
}

void sigint(int signo) {
    signal(SIGINT,sigint); /* reset signal */
    printf("CHILD: I have received a SIGINT\n");
}

void sigquit(int signo) {
    printf("My DADDY has Killed me!!!\n");
    exit(0);
}

Also, you shouldn't be using signal(2): it is unreliable in many ways, and its exact semantics are platform dependent. To ensure maximum portability, you should use sigaction(2). Refer to the manpages to learn more. Here's the same code using sigaction(2) instead:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

void sighup(int); /* routines child will call upon sigtrap */
void sigint(int);
void sigquit(int);

int main(void) {

    struct sigaction sigact;
    sigact.sa_flags = 0;
    sigemptyset(&sigact.sa_mask);

    sigact.sa_handler = sighup;
    if (sigaction(SIGHUP, &sigact, NULL) < 0) {
        perror("sigaction()");
        exit(1);
    }

    sigact.sa_handler = sigint;
    if (sigaction(SIGINT, &sigact, NULL) < 0) {
        perror("sigaction()");
        exit(1);
    }

    sigact.sa_handler = sigquit;
    if (sigaction(SIGQUIT, &sigact, NULL) < 0) {
        perror("sigaction()");
        exit(1);
    }

    pid_t pid;
    /* get child process */
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {

        /* child */
        for(;;); /* loop for ever */

    } else {

        sigact.sa_handler = SIG_DFL;
        sigaction(SIGHUP, &sigact, NULL);
        sigaction(SIGINT, &sigact, NULL);
        sigaction(SIGQUIT, &sigact, NULL);

        /* parent */
        /* pid hold id of child */
        printf("\nPARENT: sending SIGHUP\n\n");
        kill(pid,SIGHUP);
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGINT\n\n");
        kill(pid,SIGINT);
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGQUIT\n\n");
        kill(pid,SIGQUIT);
        sleep(3);
    }

    return 0;
}

void sighup(int signo) {
    signal(SIGHUP,sighup); /* reset signal */
    printf("CHILD: I have received a SIGHUP\n");
}

void sigint(int signo) {
    signal(SIGINT,sigint); /* reset signal */
    printf("CHILD: I have received a SIGINT\n");
}

void sigquit(int signo) {
    printf("My DADDY has Killed me!!!\n");
    exit(0);
}

Last, but not least, it is important to mention that you should always compile with -Wall. Your program has some mistakes:

  • The return type of main() should be int.
  • Signal handlers receive the signal number as an argument, please use the right prototype and declaration.
  • fork(2) returns a pid_t, not an int, please use the correct type.
  • You need to include unistd.h to get the right prototype for fork(2).
  • printf(3) is not async-signal safe and as such you shouldn't call it inside a signal handler. It's ok in this toy program to see how signals work together, but keep in mind that you should never do it in the real world. To see a list of async-signal safe functions, as well as the default actions for each signal, see man 7 signal.

Word of advice: stop learning from that website. If you want to learn this kind of stuff, read Advanced Programming in the UNIX Environment. Go straight to chaper 10 to learn why exactly signal(2) is considered unreliable and obsolescent. It's a big book, but it's well worth investing your time on it.