Signals are asynchronous events which can cause a process to jump to a function called a signal handler. Signals can be raised because of an external event (like ^C was pressed, raising SIGINT) or because of hardware failure or invalid memory access, or even just because another process wanted to raise one.
There are three things a process can do with a signal:
Here is a partial list of signals. Ones of interest are marked in bold.
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. |
In a nutshell, don't use it. It is the old unreliable method of delivering signals and it full of race conditions you can easily do without. The sigaction() method is far superior.
In the new reliable system, signals are first generated. From that time until the signal is delivered to the process, it is said to be pending. A process can force a signal to remain pending by blocking that signal. The signal will be delivered once it is unblocked. Each process maintains a mask of signals which are currently blocked, called the signal mask.
Many of the signal operations require the use of a set of signals, which is stored in a sigset_t. This variable is operated on by a number of set functions:
For an example, lets write a program that catches SIGINT, which is what you get when you hit ^C from the terminal. Instead of the default action (which is to exit the program), we'll have it print "Ouch!"
When catching a signal, you have the ability to mark other signals as blocked while the signal is being handled. Note that the signal that is being handled is automatically added to the set of signals that are blocked while the program is executing the signal handler. This means that even if you hit ^C again while it's in the handler, it won't reenter it. In this example, we don't care if the SIGINT handler is interrupted by any other signals, so we'll just use sigemptyset() to clear the set.
Here is the prototype for sigaction():
#include <signal.h> int sigaction(int signum, struct sigaction *act, struct sigaction *oldact);
The first argument is the signal you want to handle. The second and
third arguments are both pointers to
What does the
struct sigaction { void (*sa_handler)(int); /* pointer to the handler function */ sigset_t sa_mask; /* other signals to be blocked */ int sa_flags; /* controls specific behavior */ }
You put a pointer to the handler in sa_handler, a sigset_t in sa_mask that describes which other signals are to be blocked when the signal is being handled, and sa_flags, which is a bitwise-ORd set of flags.
The first argument, sa_handler, in addition to being set to point at the handler function, can be one of two special macros: SIG_IGN or SIG_DFL, to ignore the signal or restore the default handler, respectively. What is a pointer to a function? Well if you have a function called:
void sigint_handler(int signo);
then a pointer to that function is simply referred to as "sigint_handler":
sa.sa_handler = sigint_handler;
The second argument, sa_mask should be constructed with the signal set operations previously mentioned.
Finally, sa_flags can be one of a lot of things, but you should probably just set it to SA_RESTART now which tells the kernel to automatically restart interrupted slow system calls (like read() or other calls that can block forever).
Here is an example which catches SIGINT (^C) and prints "Ouch!" instead of exiting the program. Meanwhile, it's counting down from 10 to 1. Notice how the call to sleep can be interrupted and broken out of; if you hold down ^C, it finishes in well under 10 seconds.
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> main() { void sigint_handler(int signo); int i; struct sigaction sa; char s[100]; sigset_t mask; sigemptyset(&mask); /* we don't care to block any other signals */ sa.sa_handler = sigint_handler; sa.sa_mask = mask; sa.sa_flags = SA_RESTART; /* restart slow system calls */ sigaction(SIGINT, &sa, NULL); /* you should error-check this! */ for(i = 10; i >= 1; i--) { printf("%d\n", i); sleep(1); } } void sigint_handler(int signo) { printf("Ouch!\n"); }
There are a few ways to generate signals: kill(), raise(), abort(), and alarm(), are a few of these.
If you want to send a signal to your own process, use the raise() call with the signal number as the argument. For instance, if you want to make it appear that ^C was pressed, you might:
raise(SIGINT);
If you want to be able to send a signal to a specific PID, use kill(). (Incidentally, the kill() system call is the basis for the Unix kill command.) You simply need to pass a PID and the signal number to generate:
kill(SIGINT, 13245);
Feature: you can pass 0 as the signal number. This is known as the null signal. If you do kill() with the null-signal, no signal is actually generated for the target PID; it doesn't even know anything happened. What's the use? Doing this can allow you to check the existence of the target process--if it doesn't exist, kill() will return error. If it does exist, kill() will succeed, but there will be no effect.
In case of very grave and serious catastrophic failure of something or another, your program might want to call abort(). It takes no arguments.
abort() will raise the SIGABRT signal which has a default action of "terminate and dump core". Even if you write a signal handler for this signal, the process will still terminate once the handler returns. (You can siglongjump() out of there still, though...why you'd want to do this is a good question.)
The alarm() call can be used to raise SIGALRM some number of seconds in the future. (The Unix ping command uses alarm() each second to send out another ping packet.) The default action for this signal is to exit the process--be sure to write a handler if you want something else to happen.
Raise SIGALRM in 10 seconds:
alarm(10);
Note: alarm() returns the number of seconds remaining in the previous alarm. For instance if you call alarm(10), but only 6 seconds elapse before it is interrupted, the next call to alarm() will return 4. If you just want to check to see how much time remains on the previous alarm but don't want to schedule a new one, you can call alarm(0).
So that you don't have to always check to see if read()
returned EINTR, you can specify the SA_RESTART flag in
your
Linux seems to ignore this flag, restarting them all automatically.
You can command the kernel to block certain signals from occurring using this system call. The prototype is:
int sigprocmask(int how, sigset_t *set, sigset_t *oldset);
The first argument, how, tells the kernel what to do with the set of signals to be blocked, set. It can be one of the following:
The second argument, set, can also be NULL. If it is, the signal mask will not be modified, but the current signal mask will be returned in oldset.
The final argument, oldset, will contain the old signal mask before the modification. You can set this to NULL if you don't care what the signal mask used to be.
This system call will return a sigset_t containing all the signals that are pending. The prototype is:
int sigpending(sigset_t *set);
The pending signals can be found by running set through sigismember(), described above.
These work just like setjmp() and longjmp(), except they return the signal mask to normal if used within a signal handler. (Remember how the signal that's being currently handled is automatically added to the signal mask? Using these inside the handler will restore the proper mask settings.)
You can halt the execution of your program until any signal occurs by calling pause(). It takes no arguments and always returns -1 and sets errno to EINTR, so you don't have to error check it.
There are times when you want to set the signal mask (which might be pending) and then wait for those signals to be handled before continuing. Often you'll want to do this in an atomic operation if you're blocking signals around a critical section of code.
This call, sigsuspend(), is like sigprocmask() (with SIG_SETMASK) and pause() bundled into one:
int sigsuspend(sigset_t *mask);
Like pause(), it always returns -1 and sets errno to EINTR.