Signals
A signal is a short message that interrupts a process: a timer, a terminal control sequence, or another process asking it to stop. The program installs a handler, and the kernel calls it at the next opportunity. The handler runs in the middle of whatever the program was doing, which is what makes the rules around it strict.
A signal handler
signal installs a function for one signal number and returns the previous handler, or SIG_ERR. The program raises SIGUSR1 at itself, which is the portable way to test a handler without another process. The handler prints the number it received.
#include <signal.h>
#include <stdio.h>
static void on_signal(int number)
{
printf("caught %d\n", number);
}
int main(void)
{
if (signal(SIGUSR1, on_signal) == SIG_ERR) {
return 1;
}
printf("raising\n");
raise(SIGUSR1);
printf("after\n");
return 0;
}
gcc -Wall -Wextra handler.c -o handler
./handler
raising
caught 10
after
The number 10 is SIGUSR1 on this system; the macro is the portable name. The handler returned, so the program continued with the line after raise. In a real handler that line might be anywhere, which is why the handler should only set a flag.
sigaction
sigaction is the modern interface: it takes a struct with the handler, a mask of signals to block during the handler and a set of flags. It also reports the previous action when asked. The example installs a handler for SIGUSR2 and raises it, and the output is the same shape as before.
#include <signal.h>
#include <stdio.h>
#include <string.h>
static void on_signal(int number)
{
printf("action caught %d\n", number);
}
int main(void)
{
struct sigaction action;
memset(&action, 0, sizeof action);
action.sa_handler = on_signal;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
if (sigaction(SIGUSR2, &action, NULL) != 0) {
perror("sigaction");
return 1;
}
raise(SIGUSR2);
printf("after\n");
return 0;
}
gcc -Wall -Wextra action.c -o action
./action
action caught 12
after
12 is SIGUSR2 here. The zeroed struct plus sigemptyset means no extra signals are blocked while the handler runs; a handler that touches shared state adds the signals it must not be interrupted by to the mask.
alarm and pause
alarm asks the kernel to send SIGALRM after a number of seconds, and pause sleeps until any signal arrives. Together they are a timer: install the handler, set the alarm, wait. The program below takes about a second to run.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static void on_alarm(int number)
{
printf("alarm %d\n", number);
}
int main(void)
{
if (signal(SIGALRM, on_alarm) == SIG_ERR) {
return 1;
}
alarm(1);
pause();
printf("done\n");
return 0;
}
gcc -Wall -Wextra alarm.c -o alarm
./alarm
alarm 14
done
14 is SIGALRM. The handler runs when the timer fires, pause returns, and the program prints its last line. A timeout around a blocking call uses the same three steps, with the handler setting a flag instead of printing.
Signals that cannot be caught
Two signals are reserved for the kernel: SIGKILL ends the process and SIGSTOP suspends it, and neither can be caught, blocked or ignored. The attempt fails immediately with SIG_ERR and errno set to EINVAL, and the program can report it before doing anything else.
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
static void on_signal(int number)
{
(void) number;
}
int main(void)
{
errno = 0;
if (signal(SIGKILL, on_signal) == SIG_ERR) {
printf("cannot catch SIGKILL: %s\n", strerror(errno));
return 1;
}
return 0;
}
gcc -Wall -Wextra sigkill.c -o sigkill
./sigkill; echo "status: $?"
cannot catch SIGKILL: Invalid argument
status: 1
The error is the portable answer: the program asked for something the kernel refuses, and the message comes from strerror. A handler cannot be installed for this signal, so a service that must survive every termination request has to handle SIGTERM, which is catchable, and treat SIGKILL as the last resort of the administrator.
Exercises
- Install a handler for SIGINT that prints a message, run the program in the terminal and press Ctrl-C.
- Use a flag set by the SIGALRM handler to stop a loop instead of printing inside the handler.
- Ask sigaction for the previous action of SIGTERM with a null new action and print the handler address.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does
signalreturn on success?Answer
The previous handler for that signal
The old handler is returned, and
SIG_ERRreports failure, which lets a program restore or check the action. -
Why should a signal handler only set a flag?
Answer
It interrupts code at an arbitrary point
The handler runs in the middle of the program, so it must avoid operations that are unsafe to interrupt.
-
What does
alarm(1)do?Answer
It schedules
SIGALRMafter one secondThe kernel sends the signal after the requested number of seconds, and
pausewaits for it. -
Which two signals cannot be caught or ignored?
Answer
SIGKILLandSIGSTOPThe kernel reserves those two for termination and suspension, so an attempt to install a handler fails.
-
What does this program print?
#include <signal.h> #include <stdio.h> static void on_signal(int n){printf("caught %d\n",n);} int main(void){signal(SIGUSR1,on_signal);printf("raising\n");raise(SIGUSR1);printf("after\n");return 0;}Answer
raising / caught 10 / after
The program prints before the raise, the handler prints the signal number, and the program continues afterwards.
-
What does this program print?
#define _POSIX_C_SOURCE 200809L #include <signal.h> #include <stdio.h> #include <string.h> static void on_signal(int n){printf("action caught %d\n",n);} int main(void){struct sigaction a;memset(&a,0,sizeof a);a.sa_handler=on_signal;sigemptyset(&a.sa_mask);sigaction(SIGUSR2,&a,NULL);raise(SIGUSR2);printf("after\n");return 0;}Answer
action caught 12 / after
SIGUSR2is 12 here, and the handler returns so the final line runs. -
What does this program print?
#include <signal.h> #include <stdio.h> #include <unistd.h> static void on_alarm(int n){printf("alarm %d\n",n);} int main(void){signal(SIGALRM,on_alarm);alarm(1);pause();printf("done\n");return 0;}Answer
alarm 14 / done
The one-second timer sends
SIGALRM(14), the handler prints, andpausereturns. -
What does an attempt to catch
SIGKILLreport?Answer
cannot catch SIGKILL: Invalid argument, status 1
signalreturnsSIG_ERRwitherrnoset toEINVAL, and the program prints the system message and exits 1. -
What does the handler that only sets a flag allow the main loop to do?
Answer
Stop at a safe point instead of inside the handler
The loop checks the flag between operations, so no unsafe work happens in the signal context.
-
Why is
printfinside a signal handler risky?Answer
It is not async-signal-safe and may deadlock
The library may hold a lock that the interrupted code already owns, so the handler should only touch safe objects.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev