Linux System Programming · intermediate · ~8 min

Common mistakes with signals

Recognize the bugs that catch almost every C programmer the first time they work with signals.

Lesson

Signals are easy to install but easy to misuse. Here are the mistakes that bite most people, and how to avoid each one.

1. Calling printf or malloc from a handler

A signal handler runs at an unpredictable moment, possibly in the middle of another call. Functions like printf and malloc are not async-signal-safe (safe to call from a handler), so calling them there can corrupt state or deadlock.

  • Use write() to print.
  • Or set a flag in the handler and do the real work later in your main loop.

2. Using a plain int flag

A signal can arrive between the read and the write of an ordinary variable, leaving it in an inconsistent state.

  • Declare the flag as volatile sig_atomic_t. This type is guaranteed safe to read and write atomically across a signal.

3. Trying to catch SIGKILL or SIGSTOP

This cannot be done. The kernel refuses to install a handler for these two signals.

4. No SIGCHLD handler in a server

When a child process exits, it stays as a zombie (a finished process the parent has not yet collected) until the parent reaps it. Without a SIGCHLD handler, zombies accumulate forever.

5. Forgetting EINTR

When a signal interrupts a blocking system call, that call returns -1 with errno == EINTR.

  • Retry the call, or
  • Install the handler with the SA_RESTART flag so the kernel restarts the call automatically.

6. Race between alarm(0) and the signal

alarm(0) cancels a pending alarm. But if the kernel has already queued the delivery, there is a tiny window where the signal still arrives.

  • Do not assume cancellation succeeded. Always check the operation's actual result.

7. Re-installing the handler inside the handler

This is only a concern with the legacy signal() function. Use sigaction() instead and the problem 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

  • Do not assume signal() is fine just because it works on your Linux box. Its behavior is not portable. The same code can break on a BSD system and crash. Use sigaction() instead.

Summary

Six recurring signal bugs to memorize:

  • Calling stdio or other unsafe functions inside a handler.
  • Using a non-atomic flag instead of volatile sig_atomic_t.
  • Trying to catch SIGKILL or SIGSTOP.
  • Leaving out a SIGCHLD reaper and letting zombies pile up.
  • Ignoring EINTR on interrupted system calls.
  • Assuming alarm() cancellation has no race.

Practice with these exercises