Signals
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.
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
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.
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.
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”.)
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.
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
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.
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.)
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
sa_handler The signal handler function (or SIG_IGN to ignore the signal)
sa_mask A set of signals to block while this one is being handled
sa_flags Flags to modify the behavior of the handler, or 0
What about that sa_mask field? When you're handling a signal, you might want to block other signals from being delivered, and you can do this by adding them to the sa_mask It's a “set”, which means you can do normal set operations to manipulate them: sigemptyset(), sigfillset(), sigaddset(), sigdelset(), and sigismember(). In this example, we'll just clear the set and not block any other signals.
Here's one that handled SIGINT, which can be delivered by hitting ^C
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
void sigint_handler(int sig)
{
write(0, "Ahhh! SIGINT!\n", 14);
}
int main(void)
{
void sigint_handler(int sig); /* prototype */
char s[200];
struct sigaction sa;
sa.sa_handler = sigint_handler;
sa.sa_flags = 0; // or SA_RESTART
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
printf("Enter a string:\n");
if (fgets(s, sizeof s, stdin) == NULL)
perror("fgets");
else
printf("You entered: %s\n", s);
return 0;
}
Enter a string:
the quick brown fox jum^CAhhh! SIGINT!
fgets: Interrupted system call
This program has two functions: main() which sets up the signal handler (using the sigaction() 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.
You might be able to set your sa_flags to include SA_RESTART.
sa.sa_flags = SA_RESTART;
Enter a string:
Hello^CAhhh! SIGINT!
Er, hello!^CAhhh! SIGINT!
This time fer sure!
You entered: This time fer sure!
Some system calls are interruptible, and some can be restarted. It's system dependent.
You have to be careful when you make function calls in your signal handler. Those functions must be “async safe”, so they can be called without invoking undefined behavior.
You might be curious, for instance, why my signal handler, above, called write() to output the message instead of printf(). Well, the answer is that POSIX says that write() is async-safe (so is safe to call from within the handler), while printf() is not.
You also cannot safely alter any shared (e.g. global) data, with one notable exception: variables that are declared to be of storage class and type volatile sig_atomic_t.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
volatile sig_atomic_t got_usr1;
void sigusr1_handler(int sig)
{
got_usr1 = 1;
}
int main(void)
{
struct sigaction sa;
got_usr1 = 0;
sa.sa_handler = sigusr1_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
while (!got_usr1) {
printf("PID %d: working hard...\n", getpid());
sleep(1);
}
printf("Done in by SIGUSR1!\n");
return 0;
}
$ sigusr
PID 5023: working hard...
PID 5023: working hard...
PID 5023: working hard...
Fire it it up in one window, and then use the kill -USR1 in another window to kill it.
$ kill -USR1 5023
PID 5023: working hard...
PID 5023: working hard...
Done in by SIGUSR1!
And the response should be immediate even if sleep() has just been called—sleep() gets interrupted by signals.