~/learn

Signals

Signals as asynchronous software interrupts — the common signals, sending them with kill, and handling them reliably with sigaction.

A signal is an asynchronous notification delivered to a process — a lightweight "software interrupt". The kernel, another process, or the terminal can send one at any time; the process can catch it, ignore it, or let the default action happen. Signals are the first and simplest form of inter-process communication we meet.

What is a signal?

Signals come from many sources — the kernel (on a fault), the terminal driver (Ctrl+C), another process (kill), a timer (alarm) — and follow a simple lifecycle: generation → delivery → handling. When one is delivered, the process's normal flow is interrupted to run the signal's disposition, then resumes.

Common signals

SignalMeaning
SIGTERMpolite request to terminate (default kill)
SIGKILLforced termination — cannot be caught or ignored
SIGINTinterrupt from the keyboard (Ctrl+C)
SIGTSTP / SIGCONTsuspend (Ctrl+Z) / resume
SIGSTOPforced suspend — cannot be caught
SIGSEGV / SIGFPE / SIGILLsegfault / bad arithmetic / illegal instruction
SIGCHLDa child process stopped or terminated
SIGUSR1 / SIGUSR2free for your own use
SIGALRMa timer set with alarm() expired
SIGPIPEwrote to a pipe/socket with no reader

Sending signals

  • kill(pid, sig) — send a signal to a process (or process group)
  • raise(sig) — send one to yourself
  • pthread_kill(thread, sig) — target a specific thread
  • From the shell: kill, killall, pkill

Handling signals

A process chooses a disposition per signal: the default action, ignore it (SIG_IGN), or run a custom handler. Use sigaction() — the modern, reliable API (the old signal() has portability pitfalls, so avoid it):

#include <signal.h>
#include <stdio.h>

void on_sigint(int sig) {
    // (only async-signal-safe calls belong here)
    write(1, "caught SIGINT\n", 14);
}

int main(void) {
    struct sigaction sa = {0};
    sa.sa_handler = on_sigint;
    sigemptyset(&sa.sa_mask);   // signals blocked during the handler
    sa.sa_flags = SA_RESTART;   // auto-restart interrupted syscalls
    sigaction(SIGINT, &sa, NULL);

    for (;;) pause();           // wait for signals
}

The struct sigaction also offers sa_sigaction (an extended handler with siginfo_t when you set SA_SIGINFO), sa_mask (extra signals to block while the handler runs), and flags like SA_RESTART and SA_NODEFER.

Handler safety

A handler interrupts your program at an arbitrary point, so only call async-signal-safe functions inside it (e.g. write, not printf). SIGKILL and SIGSTOP can never be caught, handled, or ignored.

Timers

alarm(seconds) schedules a one-shot SIGALRM; setitimer() provides repeating, finer-grained timers. Combined with a handler, they implement timeouts.

Practice / lab

  • Catch SIGINT with sigaction and print a message instead of terminating; send it with Ctrl+C and with kill -INT <pid>.
  • Fork a child, have the parent send SIGUSR1 with kill(), and let the child respond with SIGUSR2.
  • Use alarm() to implement a timeout around a blocking read.

Homework

  • IPC-1: Signal Echo — print details of the sending process from inside a SIGUSR1 handler.

References & resources

Required

Optional / enrichment

Self-check

  1. What is the difference between SIGTERM and SIGKILL?
  2. Why is sigaction() preferred over signal()?
  3. What are the possible dispositions for a signal?
  4. Why must you only call async-signal-safe functions inside a handler?
  5. How does alarm() use signals to implement a timeout?

On this page