What is the signal() function, and why is it considered unreliable?
signal() is the original (pre-POSIX) API for installing signal handlers.
Prototype:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
Why it is "unreliable":
Handler reset: On some systems (System V), after the handler is called, the disposition is reset to
SIG_DFL. If another signal arrives before the handler reinstalls itself, it gets the default action (which may terminate the process). This creates a race window.No signal blocking: The signal is not blocked while the handler runs. If the same signal arrives again during handler execution, the handler is interrupted recursively.
Implementation varies: BSD systems do NOT reset the handler and DO block the signal during handling. This inconsistency across platforms makes
signal()non-portable.EINTR behavior: No control over whether interrupted system calls are restarted.
Solution: Use sigaction() instead. It gives explicit control over:
- Whether the handler resets (
SA_RESETHAND) - What signals are blocked during handling (
sa_mask) - Whether to restart interrupted syscalls (
SA_RESTART) - Whether to use the old or new signal handler prototype
signal() problems:
1. Handler installed
2. Signal arrives -> handler called
3. On System V: disposition reset to SIG_DFL (race window!)
4. Another signal arrives before handler reinstalls -> DEFAULT ACTION
sigaction() fixes:
1. Handler installed with sigaction()
2. Signal arrives -> signal blocked -> handler called
3. Handler returns -> signal unblocked
4. No reset, no race window
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
/* Using signal() -- unreliable on System V */
void old_handler(int sig) {
/* On System V, disposition is now SIG_DFL!
Another SIGINT here would kill the process */
signal(sig, old_handler); /* try to reinstall (race!) */
printf("Caught signal %d (unreliable)\n", sig);
}
/* Using sigaction() -- reliable */
void safe_handler(int sig) {
/* Signal is blocked during this handler (no race) */
/* Disposition is NOT reset */
const char msg[] = "Caught signal (reliable)\n";
write(STDOUT_FILENO, msg, sizeof(msg) - 1); /* async-signal-safe */
}
int main(void) {
/* Unreliable way (avoid in production) */
signal(SIGINT, old_handler);
/* Reliable way (always prefer this) */
struct sigaction sa;
sa.sa_handler = safe_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; /* restart interrupted syscalls */
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("Press Ctrl+C to test (pid=%d)\n", getpid());
pause(); /* wait for signal */
printf("Returned from pause\n");
return 0;
}
- •signal() behavior varies across platforms (System V vs BSD)
- •System V resets handler to SIG_DFL after delivery (race condition)
- •signal() does not block the signal during handler execution on some systems
- •sigaction() provides reliable, portable signal handling
- •Always prefer sigaction() over signal() in production code
- •signal() semantics differ between Linux (BSD-like) and older System V
- •Reinstalling the handler inside signal() has a race window
- •signal() gives no control over SA_RESTART behavior
- •The return value of signal() is the previous handler (or SIG_ERR on error)