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

  1. Install a handler for SIGINT that prints a message, run the program in the terminal and press Ctrl-C.
  2. Use a flag set by the SIGALRM handler to stop a loop instead of printing inside the handler.
  3. 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.

  1. What does signal return on success?

    Answer

    The previous handler for that signal

    The old handler is returned, and SIG_ERR reports failure, which lets a program restore or check the action.

  2. 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.

  3. What does alarm(1) do?

    Answer

    It schedules SIGALRM after one second

    The kernel sends the signal after the requested number of seconds, and pause waits for it.

  4. Which two signals cannot be caught or ignored?

    Answer

    SIGKILL and SIGSTOP

    The kernel reserves those two for termination and suspension, so an attempt to install a handler fails.

  5. 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.

  6. 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

    SIGUSR2 is 12 here, and the handler returns so the final line runs.

  7. 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, and pause returns.

  8. What does an attempt to catch SIGKILL report?

    Answer

    cannot catch SIGKILL: Invalid argument, status 1

    signal returns SIG_ERR with errno set to EINVAL, and the program prints the system message and exits 1.

  9. 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.

  10. Why is printf inside 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