7
3. Signals
There is a 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's signal handler
(just a function) is invoked and the process can handle it.
The devil's in the details, of course, and in actuality what you are permitted to do safely inside your
signal hander is rather limited. Nevertheless, signals provide a useful service.
For example, 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”.)
3.1. Catching Signals for Fun and Profit!
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 call
sigaction() and tell it all the gritty details about which signal you want to catch and which function
you want to call to handle it.
Here's the sigaction() breakdown:
int sigaction(int sig, const struct sigaction *act,
struct sigaction *oact);
The first parameter, sig is which signal to catch. This can be (probably “should” be) a symbolic
name from signal.h along the lines of SIGINT. That's the easy bit.
The next field, act is a pointer to a struct sigaction which has a bunch of fields that you
can fill in to control the behavior of the signal handler. (A pointer to the signal handler function itself
included in the struct.)
Lastly oact can be NULL, but if not, it returns the old signal handler information that was in place
before. This is useful if you want to restore the previous signal handler at a later time.
We'll focus on these three fields in the struct sigaction:
Signal Description