Signal
- Tags
- comp-arch
Is a software level interrupt, providing a rudimentary level of inter-process communication and allowing the kernel to signal events to a process.
When a process receives a signal it stops the execution of the current program and makes it respond to the signal. You can define custom signal handlers for some signals using the signal system call.
Signals may be initialised by the OS kernel (example: SIGFPE, SIGSEGV, SIGIO) or by a process such as kill. They are eventually delivered by the kernel to the target process/thread which invoke either a generic action (ignore, terminate, dump core, etc) or a process provided signal handler.
Name | Portable Number | Default Action | Usual Use |
---|---|---|---|
SIGINT | 2 | Terminate (Can be caught) | Stop a process nicely |
SIGQUIT | 3 | Terminate (Can be caught) | Stop a process harshly |
SIGTERM | 15 | Terminate Process | Stop a process even more harshly |
SIGSTOP | N/A | Stop Process (Cannot be caught) | Suspends a process |
SIGCONT | N/A | Continues a process | Starts after a stop |
SIGKILL | 9 | Terminate Process (Cannot be caught) | You want the process gone |
Sending Signals
You can generate a signal using the morbidly named kill syscall, or the kernel may
generate one in response to a hardware interrupt, or you may trigger it interactively
such as by entering CTRL-C
in the terminal which sends SIGINT
to the current process.
You can also send a signal to your own process by using the raise syscall.
State | Description |
---|---|
Generated | When a process generates a signal |
Pending | The process blocked delivery of the signal |
Blocked | When the kernel is about to deliver the signal |
Received | The signal has been delivered to the process |
Handling Signals
Signal handlers are user-defined functions that can be executed when a signal is delivered instead of the default signal handlers. You can set the handler for a given signal using the signal sys-call. Note: You don't have to handle signals synchronously, you can declare an asynchronous signal handler with sigaction.
There are strict limitations on executable code inside a signal handler. Most library and syscalls are async-signal-unsafe meaning they can't be used inside a signal handler because they aren't re-entrant. A safe way to deal with signal handlers is to set a variable in the handler body and let the program resume operating. The program should later check the variable and respond at a more appropriate time.
// Prevent multi-threading shenanigans and compiler
// optimisation optimising this variable out.
volatile sig_atomic_t please_stop;
void handle_sigint(int signal) {
please_stop = 1;
}
int main() {
signal(SIGINT, handle_sigint);
pleast_stop = 0;
while (!please_stop) {
// Application logic here.
}
// Clean up code here.
}
struct sigaction sa;
sa.sa_handler = myhandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL)
Waiting for Pending Signals
You can wait for a pending signal using the sigwait sys-call. This blocks the current thread until a signal arrives and lets you handle it directly rather than through a callback.
One possible use case for this is to create a separate thread for handling incoming signals that can doesn't have the strict requirements of an ordinary signal handler. With such an approach you can use async-signal-unsafe functions.
static sigset_t signal_mask;
int main(int argc, char *argv[]) {
pthread_t sig_thr_id;
sigemptyset(&signal_mask);
sigaddset(&signal_mask, SIGINT);
sigaddset(&signal_mask, SIGTERM);
// Sets the sigmask for the current thread and any threads
// made after this point.
pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
// New threads will inherit the current threads sigmask.
pthread_create(&sig_thr_id, NULL, signal_thread, NULL);
// ... Application Code.
}
void *signal_thread(void *arg) {
int sig_caught;
// Use the same mask as the set of signals that we'd like to know about!
sigwait(&signal_mask, &sig_caught);
switch (sig_caught) {
case SIGINT:
// ...
break;
case SIGTERM:
// ...
break;
default:
fprintf(stderr, "Unexpected signal %d\n", sig_caught);
break;
}
}
Ignoring Signal with a Signal Mask
The signal mask is the set of signals whose delivery is currently blocked for the caller. You can set a new mask or modify the existing masks with sigprocmask.
Never Use SIGKILL
Here's a friendly message from the UNIX useless awards:
No, no, no. Don’t use kill -9.
It doesn’t give the process a chance to cleanly:
- shut down socket connections
- clean up temp files
- inform its children that it is going away
- reset its terminal characteristics
and so on and so on and so on.
Generally, send 15, and wait a second or two, and if that doesn’t work, send 2, and if that doesn’t work, send 1. If that doesn’t, REMOVE THE BINARY because the program is badly behaved!
Don’t use kill -9. Don’t bring out the combine harvester just to tidy up the flower pot.