Search code examples
csignalssigaction

I want to use sigaction(), but I've a problem


I want to make a program that If process receive SIGQUIT, print some message and terminate the process. and here is my code,

void signal_handler(int num)
{
  printf(Received SIGQUIT\n");
  kill(getpid(), SIGINT);
}

int main()
{
  struct sigaction sig;
  sig.sa_handler = signal_handler;
  sigemptyset(&sig.sa_mask);
  sig.sa_flags = 0;
  sigaction(SIGQUIT, &sig, NULL);

  for(;;) {
    printf("Input SIGQUIT : ");
  }

  return 0;
}

my expected result is

Input SIGQUIT: ^\ Received SIGQUIT

and terminate process,

but the actual result is

enter image description here

the message "Input SIGQUIT:" is not printed before receive SIGQUIT

I want to know why..


Solution

  • First: the code you cite does not compile. Please post valid code; this increases your chances to get an answer.

    I'll take the freedom to reformulate your question to something like,

    How to I terminate a program gracefully?

    One should not reformulate questions, I know. But in the interest of sanity I have to spread the word that asynchronous signal delivery is a mistake that was made when UNIX was invented. Read on below for a little explanation of why, and some alternatives. But first,

    TL;DR: do not use asynchronous signal delivery. It is very hard to get asynchronous signal delivery right - there are many traps (thanks @Shawn for the comment). Rather, synchronously wait for the signal to arrive. Here's how that would look like,

    #include <signal.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    
    
    int main(void)
    {
        int error;
    
        // setup set of signals that are meant to terminate us
        sigset_t termination_signals;
        sigemptyset(&termination_signals);
        sigaddset(&termination_signals, SIGTERM);
        sigaddset(&termination_signals, SIGINT);
        sigaddset(&termination_signals, SIGQUIT);
    
        // block asynchronous delivery for those
        error = sigprocmask(SIG_BLOCK, &termination_signals, NULL);
        if (error) {
            perror("sigprocmask(SIGTERM|SIGINT|SIGQUIT)");
            exit(1);
        }
    
        // wait for one of these signals to arrive. EINTR handling is
        // always good to have in larger programs. for example, libraries
        // might make use of signals in their own weird way - thereby
        // disturbing their users most impolitely by interrupting every
        // operation they synchronously wait for.
        while (1) {
            int sig;
            error = sigwait(&termination_signals, &sig);
            if (error && errno == EINTR) {
                perror("sigwait");
                continue;
            }
    
            printf("received termination signal %d\n", sig);
            break;
        }
    
        return 0;
    }
    

    Asynchronous Signal Delivery

    Signals are a poor man's notification. It's like throwing integer values (with predefined meanings) at processes. Originally, as they were inventing UNIX, they had to come up with a notification mechnism to use between processes. They did this in a way that is an analogy to a then-popular notification mechnism: interrupts. (While this might sound cynical, I'd bet that I'm not far off.)

    Questions to ask yourself before you really decide to go that route:

    1. Do I really know that I need it?
    2. What are the consequences?
    3. Are there alternatives?

    While I cannot help with 1., here some information for 2. and 3.

    Consequences of Asynchronous Signal Handling

    Asynchronous signal delivery is causing your program to enter a certain form of parallelism: the signal handler interrupts normal program flow. Such interruptions might not happen at times where the program is prepared for them.

    The interruption mechnism is comparable to what you might know from multithreading, at a highest level. A common denominator of those two might be that, "if you don't take care you are dead, but you might not even know". Race conditions.

    Asynchronous signal delivery has nothing in common with multithreading though, apart from increasing mortality rates.

    As if that's not enough, there's the concept of interrupted system calls.

    Rationale goes like this (taken from a hypothetical conversation between Ken Thompson and Dennis Ritchie, right before The Epoch),

    Q: now we have defined a notification mechnism between processes (asynchronous signal delivery), and jumped through hoops to call a user supplied function to handle delivery.

    Now what if the target process sleeps? Waits for something to happen? Timer? Data from a serial port maybe?

    A: let's wake him up!

    The effect of this is that blocking system calls (like reading from a TCP connection - these haven't been there at that time - , or from serial ports) are interrupted. When you read from a socket, or otherwise block until some event occurs, the call return non-zero, with the global errno variable (another artifact of the early seventies) being set to EINTR.

    It is still unclear to me what the intended behavior is when the target process has multiple threads where each thread blocks on something. I'd expect that every such blocking call is interrupted. Unfortunately behavior is not consistent, not even on Linuxen, let alone other UNIXen that I haven't used for a long time. I'm not a standards lawyer, but this alone make me run away from this arcane mechanism.

    That said, if you are still not running away, please inform yourself about the exact definitions. The best place to start, in my opinion, is to read the man 7 signal manual page. Not an easy read though, but exact.

    Next, to know what can be done in an interrupt service routine, err signal handler function, read through the man 7 signal-safety manual page. You'll see that, for example, none of the following are safe:

    • No printf(). You use that in the signal handler, so please don't. None of printf()'s siblings, such a fprintf() are safe either. None of <stdio.h>.
    • <pthread.h> is not safe. You cannot modify thread-safe data structures because you'd inadvertently lock a mutex, or signal (pun intended) a condition variable, or use some other threading mechanism.

    Alternatives

    Synchronously await signals. The code that I cite above does that.

    Event driven programming. The following is Linux specific.

    At the basis of that is an event loop of some form. GUI toolkits work this way, so if your code is part of such a larger picture, you can hook file descriptor based signal notifications into something that's already there.

    Event sources, at their basis, are file descriptors. Sockets work this way. See man signalfd for how to create a file descriptor to spit out signal notification.

    Event loops, at their basis, use one of the following system calls (order from simplest to most powerful),

    Self pipe trick. See here; this is commonly used when your program is event based, but you cannot used the Linux-specific signalfd() above.