Signals

There is a very easy, simple, and sometimes useful method for one process to bug another: signals. Basically, one process can "raise" a signal and have it delivered to another process. The destination process' signal handler (just a function) is invoked and the process can handle it.

For instance, one process might want to stop another one, and this can be done by sending the signal SIGSTOP to that process. To continue, the process has to receive signal SIGCONT. How does the process know to do this when it receives a certain signal? Well, many signals are predefined and the process has a default signal handler to deal with it.

A default handler? Yes. Take SIGINT for example. This is the interrupt signal that a process receives when the user hits ^C. The default signal handler for SIGINT causes the process to exit! Sound familiar? Well, as you can imagine, you can override the SIGINT to do whatever you want (or nothing at all!) You could have your process printf() "Interrupt?! No way, Jose!" and go about its merry business.

So now you know that you can have your process respond to just about any signal in just about any way you want. Naturally, there are exceptions because otherwise it would be too easy to understand. Take the ever popular SIGKILL, signal #9. Have you ever typed "kill -9 nnnn" to kill a runaway process? You were sending it SIGKILL. Now you might also remember that no process can get out of a "kill -9", and you would be correct. SIGKILL is one of the signals you can't add your own signal handler for. The aforementioned SIGSTOP is also in this category.

(Aside: you often use the Unix "kill" command without specifying a signal to send...so what signal is it? The answer: SIGTERM. You can write your own handler for SIGTERM so your process won't respond to a regular "kill", and the user must then use "kill -9" to destroy the process.)

Are all the signals predefined? What if you want to send a signal that has significance that only you understand to a process? There are two signals that aren't reserved: SIGUSR1 and SIGUSER2. You are free to use these for whatever you want and handle them in whatever way you choose. (For example, my cd player program might respond to SIGUSR1 by advancing to the next track. In this way, I could control it from the command line by typing "kill -SIGUSR1 nnnn".)

You can't SIGKILL the President!

As you can guess the Unix "kill" command is one way to send signals to a process. By sheer unbelievable coincidence, there is a system call called kill() which does the same thing. It takes for its argument a signal number (as defined in signal.h and a process ID. Also, there is a library routine called raise() which can be used to raise a signal within the same process.

The burning question remains: how do you catch a speeding SIGTERM? You need to use the signal() call and pass it a pointer to a function that is to be your signal handler. Never used pointers to functions? (You must check out the qsort() routine sometime!) Don't worry, they're simple: if "foo("hi!");" is a call to function foo(), then "foo" is a pointer to that function. You don't even have to use the address-of operator.

Anyway, here's the signal() breakdown:

    void (*signal(int sig, void (*func)(int)))(int);

What is the name of Bruce Dickinson does that mean? Well, the basic situation is this: we are going to pass the signal to be handled as well as the address of the signal handler as arguments to the signal() call. The signal handler function that you define takes a single int as an argument, and returns void. Now, the signal() call returns either an error, or a pointer to the previous signal handler function. So we have the call signal() which accepts a signal and a pointer to a handler as arguments, and returns a pointer to the previous handler. And the above code is just how we declare that.

Fortunately, using it is much easier than it looks. All you need is a handler function that takes an int as an argument and returns void. Then call signal() to set it up. Easy? Let's do a simple program that will handle SIGINT and stop the user from quitting through ^C, called sigint.c:

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

    int main(void)
    {
        void sigint_handler(int sig); /* prototype */
        char s[200];
    
                /* set up the handler */
        if (signal(SIGINT, sigint_handler) == SIG_ERR) {
            perror("signal");
            exit(1);
        }
    
        printf("Enter a string:\n");
    
        if (gets(s) == NULL)
            perror("gets");
        else 
            printf("You entered: \"%s\"\n", s);
    
        return 0;
    }
    
        /* this is the handler */
    void sigint_handler(int sig)
    {
        printf("Not this time!\n");
    }

This program has two functions: main() which sets up the signal handler (using the signal() call), and sigint_handler() which is the signal handler, itself.

What happens when you run it? If you are in the midst of entering a string and you hit ^C, the call to gets() fails and sets the global variable errno to EINTR. Additionally, sigint_handler() is called and does its routine, so you actually see:

    Enter a string:
    the quick brown fox jum^CNot this time!
    gets: Interrupted system call

Here's a vital tidbit of information that I neglected to mention earlier: when the signal handler is called, the signal handler for that particular signal is reset to the default handler! The practical upshot of this is that our sigint_handler() would trap ^C the first time we hit it, but not after that. The quick and dirty solution is to reset the signal handler within itself like so:

    void sigint_handler(int sig)
    {
        signal(SIGINT, sigint_handler); /* reset it to this function */
        printf("Not this time!\n");
    }

The problem with this setup is that it introduces a race condition. If an interrupt occurs and the handler is called, but then a second interrupts occurs before the first is able to reset the interrupt handler, the default handler will be called. Be aware that if you're expecting lots of signals this might be an issue to watch out for.

Everything you know is wrong

The signal() system call is the historical method of setting up signals. The POSIX standard has defined a whole slew of new functions for masking which signals you want to receive, checking which signals are pending, and setting up signal handlers. Since many of these calls operate on groups, or sets, of signals, there are more functions that deal with signal set manipulation.

In short, the new method of signal handling blows the old one away. I will include a description in an up-and-coming version of this document, time permitting.

Some signals to make you popular

Here is a list of signals you (most likely) have at your disposal:

Signal Description
SIGABRT Process abort signal.
SIGALRM Alarm clock.
SIGFPE Erroneous arithmetic operation.
SIGHUP Hangup.
SIGILL Illegal instruction.
SIGINT Terminal interrupt signal.
SIGKILL Kill (cannot be caught or ignored).
SIGPIPE Write on a pipe with no one to read it.
SIGQUIT Terminal quit signal.
SIGSEGV Invalid memory reference.
SIGTERM Termination signal.
SIGUSR1 User-defined signal 1.
SIGUSR2 User-defined signal 2.
SIGCHLD Child process terminated or stopped.
SIGCONT Continue executing, if stopped.
SIGSTOP Stop executing (cannot be caught or ignored).
SIGTSTP Terminal stop signal.
SIGTTIN Background process attempting read.
SIGTTOU Background process attempting write.
SIGBUS Bus error.
SIGPOLL Pollable event.
SIGPROF Profiling timer expired.
SIGSYS Bad system call.
SIGTRAP Trace/breakpoint trap.
SIGURG High bandwidth data is available at a socket.
SIGVTALRM Virtual timer expired.
SIGXCPU CPU time limit exceeded.
SIGXFSZ File size limit exceeded.
Table 1. Common signals.

Each signal has its own default signal handler, the behavior of which is defined in your local man pages.

HPUX man pages

If you don't run HPUX, be sure to check your local man pages!

Here are the man pages for some of the new POSIX signal handling routines...


Copyright © 1997 by Brian "Beej" Hall. This guide may be reprinted in any medium provided that its content is not altered, it is presented in its entirety, and this copyright notice remains intact. Contact beej@ecst.csuchico.edu for more information.