Search code examples
linuxsignals

Child handling SIGQUIT without handler?


I am learning about signals and what happens to their handlers when a process is forked. I learned from this question that "signals have to be installed in the child", and if this does not happen before a signal is sent from the parent, the handler will not be called. I wanted to find out what this meant, so used strace to see what happens. Here is my code. I am not concerned with code critique, unless I am doing something wrong that is causing my problem:

#include <cassert>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;

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("CHILD: I have received a SIGQUIT\n");
    exit(0);
}


int main(void) {

    int pid;
    cout << "Current PID is " << getpid() << endl;
    signal(SIGHUP, sighup); /* set function calls */
    signal(SIGINT, sigint);
    signal(SIGQUIT, sigquit);

    cout << "Waiting to fork..." << endl;
    int x; cin >> x;

    /* 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 */
   sleep(3);
        printf("\nPARENT: sending SIGHUP\n\n");
        kill(pid, SIGHUP);
   int err = errno;
     cout << "sent SIGHUP, err is " << err;
     
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGINT\n\n");
        kill(pid, SIGINT);
   err = errno;
     cout << "sent SIGINT, err is " << err;
     
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGQUIT\n\n");
   
        kill(pid, SIGQUIT);
      err = errno;
     cout << "sent SIGINT, err is " << err;
        sleep(3);
    }

    return 0;
}

The code pauses whilst the users enters an integer after displaying its PID so I can attach to it. Assuming the parent PID is 11256, child PID is 11313, and that I entered '11' to resume the parent; here is the output from strace:

ubuntu@ubuntu-xenial:~$ sudo strace -f -p 11256
strace: Process 11256 attached
read(0, "11\n", 1024)                   = 3
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fad6078fa10) = 11313
rt_sigaction(SIGHUP, {SIG_DFL, [HUP], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, {0x400bd7, [HUP], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [INT], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, {0x400bfe, [INT], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_DFL, [QUIT], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, {0x400c25, [QUIT], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, 8) = 0
nanosleep({3, 0}, strace: Process 11313 attached
0x7ffe3a8df8a0)       = 0
[pid 11256] write(1, "\nPARENT: sending SIGHUP\n", 24) = 24
[pid 11256] write(1, "\n", 1)           = 1
[pid 11256] kill(11313, SIGHUP)         = 0
[pid 11256] nanosleep({3, 0},  <unfinished ...>
[pid 11313] --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=11256, si_uid=1000} ---
[pid 11313] rt_sigaction(SIGHUP, {0x400bd7, [HUP], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, {0x400bd7, [HUP], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, 8) = 0
[pid 11313] write(1, "CHILD: I have received a SIGHUP\n", 32) = 32
[pid 11313] rt_sigreturn({mask=[]})     = 0
[pid 11256] <... nanosleep resumed> 0x7ffe3a8df8a0) = 0
[pid 11256] write(1, "sent SIGHUP, err is 0\nPARENT: se"..., 45) = 45
[pid 11256] write(1, "\n", 1)           = 1
[pid 11256] kill(11313, SIGINT)         = 0
[pid 11256] nanosleep({3, 0},  <unfinished ...>
[pid 11313] --- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=11256, si_uid=1000} ---
[pid 11313] rt_sigaction(SIGINT, {0x400bfe, [INT], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, {0x400bfe, [INT], SA_RESTORER|SA_RESTART, 0x7fad5fe5f4c0}, 8) = 0
[pid 11313] write(1, "CHILD: I have received a SIGINT\n", 32) = 32
[pid 11313] rt_sigreturn({mask=[]})     = 0
[pid 11256] <... nanosleep resumed> 0x7ffe3a8df8a0) = 0
[pid 11256] write(1, "sent SIGINT, err is 0\nPARENT: se"..., 46) = 46
[pid 11256] write(1, "\n", 1)           = 1
[pid 11256] kill(11313, SIGQUIT)        = 0
[pid 11256] nanosleep({3, 0},  <unfinished ...>
[pid 11313] --- SIGQUIT {si_signo=SIGQUIT, si_code=SI_USER, si_pid=11256, si_uid=1000} ---
[pid 11313] write(1, "CHILD: I have received a SIGQUIT"..., 33) = 33
[pid 11313] lseek(0, -1, SEEK_CUR)      = -1 ESPIPE (Illegal seek)
[pid 11313] exit_group(0)               = ?
[pid 11313] +++ exited with 0 +++
<... nanosleep resumed> {2, 996222312}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=11313, si_uid=1000, si_status=0, si_utime=894, si_stime=1} ---
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
write(1, "sent SIGINT, err is 0", 21)   = 21
lseek(0, -1, SEEK_CUR)                  = -1 ESPIPE (Illegal seek)

We can see the calls to rt_sigaction in the parent to install the SIGHUP, SIGINT and SIGQUIT handlers, and then also in the child for SIGHUP and SIGINT.

Question 1) Is this what is meant by "installing" the handlers in the child? I just assumed (proabably erroneously) that no system calls would be made, and that the child would just be "ready to go" after the fork, without actually re-executing code that was first carried out in the parent. Was I wrong?

Question 2) I see rt_sigaction being called in the child for SIGHUP and SIGINT, but not SIGQUIT. Yet, the handler for this is still being called in the child (we see "CHILD: I have received a SIGQUIT" being written). How is the child still handling this signal, when apparently it has not installed the handler?


Solution

  • Is this what is meant by "installing" the handlers in the child? I just assumed (proabably erroneously) that no system calls would be made, and that the child would just be "ready to go" after the fork, without actually re-executing code that was first carried out in the parent.

    A signal handler is installed by calling signal or sigaction; on Linux, both of these functions make a rt_sigaction system call.

    When a process forks, the child simply inherits all of the signal settings from its parent. No code is re-executed.

    I see rt_sigaction being called in the child for SIGHUP and SIGINT, but not SIGQUIT. Yet, the handler for this is still being called in the child (we see "CHILD: I have received a SIGQUIT" being written).

    Your sighup and sigint functions call signal, which makes a rt_sigaction system call to reinstall the handler. Your sigquit function, though, just does a printf and does not call signal.