Linux System Programming · intermediate · ~8 min

Common mistakes with signals

Recognise the bugs that bite every C programmer when they first use signals.

Lesson

  1. Calling printf/malloc from a handler. They're not async-signal-safe. Use write(), or set a flag and print later.
  2. Using a plain int flag. The signal can arrive between read and write — use volatile sig_atomic_t.
  3. Trying to catch SIGKILL or SIGSTOP. Can't be done. The kernel refuses to install the handler.
  4. No SIGCHLD handler in a server. Without one, your child processes become zombies and accumulate forever.
  5. Forgetting EINTR. A blocking syscall interrupted by a signal returns -1 with errno == EINTR. Either retry, or use SA_RESTART.
  6. Race between alarm(0) and the signal. alarm(0) cancels the pending alarm, but if the kernel already queued the delivery there's a tiny window. Always check the operation's actual result.
  7. Re-installing the handler inside the handler (only relevant if you used legacy signal()). Use sigaction() and the issue disappears.

Code examples

/* WRONG */
static int stopping = 0;
static void h(int s) { (void)s; stopping = 1; printf("stopping\n"); }

/* RIGHT */
static volatile sig_atomic_t stopping = 0;
static void h(int s) {
    (void)s;
    stopping = 1;
    const char msg[] = "stopping\n";
    write(STDERR_FILENO, msg, sizeof msg - 1);
}

Common mistakes

  • Believing that "signal works fine on my Linux" excuses you from sigaction(). It will break on the next BSD you try, and your code will crash. Use sigaction().

Summary

Six recurring bugs: stdio in handlers, non-atomic flags, catching SIGKILL/SIGSTOP, no SIGCHLD reaper, ignoring EINTR, alarm() races. Memorise them.

Practice with these exercises