22.9 — Waiting with a Mask: sigsuspend()
A common pattern is: block a signal during a critical section, then unblock it and wait for it to arrive. The naive approach has a race condition. sigsuspend() solves this with an atomic operation.
The Race Condition Problem
Consider this sequence:
| Step | Code | What Can Go Wrong |
|---|---|---|
| 1 | sigprocmask(SIG_BLOCK, &intMask, …) /* critical section */ |
OK so far |
| 2 | sigprocmask(SIG_SETMASK, &prevMask, …) /* unblock SIGINT */ | Signal arrives HERE ↓ |
| 3 | /* SIGINT arrives now — handler runs! */ | BUG! Signal was handled before pause() |
| 4 | pause(); /* wait for SIGINT */ | Waits FOREVER — signal already delivered! |
The Fix: sigsuspend() is Atomic
sigsuspend(mask) atomically: (1) replaces the process signal mask with mask, (2) suspends until a signal is caught, (3) restores the original mask. Steps 1 and 2 happen with no gap between them — the race condition is eliminated.
#include <signal.h>
int sigsuspend(const sigset_t *mask);
/* Returns -1 with errno=EINTR when a signal handler returns */
/* sigsuspend_demo.c - Correct pattern: block, critical section, sigsuspend */
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
static volatile sig_atomic_t got_sigint = 0;
static volatile sig_atomic_t got_sigquit = 0;
void handler(int sig) {
if (sig == SIGINT) got_sigint = 1;
if (sig == SIGQUIT) got_sigquit = 1;
/* Note: printf is NOT async-signal-safe, used here for demo only */
const char *msg = (sig == SIGINT) ? "Got SIGINT\n" : "Got SIGQUIT\n";
write(STDOUT_FILENO, msg, 10);
}
int main(void) {
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
/* Block SIGINT and SIGQUIT */
sigset_t block_mask, prev_mask;
sigemptyset(&block_mask);
sigaddset(&block_mask, SIGINT);
sigaddset(&block_mask, SIGQUIT);
sigprocmask(SIG_BLOCK, &block_mask, &prev_mask);
printf("PID=%d: In critical section (signals blocked)\n", getpid());
printf("Press Ctrl+C or Ctrl+\\ -- signal is queued until sigsuspend\n");
/* Simulate critical work */
sleep(3);
printf("Critical section done. Waiting for signal via sigsuspend...\n");
/* Atomically: restore original mask (unblocking signals) + suspend.
Any signal that arrived during critical section is now delivered.
sigsuspend returns after the handler runs, with the old mask restored. */
while (!got_sigquit) {
/* prev_mask allows SIGINT and SIGQUIT through */
sigsuspend(&prev_mask);
if (got_sigint) {
printf("Processed SIGINT, looping back to wait\n");
got_sigint = 0;
}
}
/* Restore original mask */
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
printf("Got SIGQUIT — exiting loop\n");
return 0;
}
$ ./sigsuspend_demo
PID=5678: In critical section (signals blocked)
Press Ctrl+C or Ctrl+\ -- signal is queued until sigsuspend
^C <-- Ctrl+C during critical section (queued)
Critical section done. Waiting for signal via sigsuspend...
Got SIGINT <-- delivered immediately by sigsuspend
Processed SIGINT, looping back to wait
^\ <-- Ctrl+\
Got SIGQUIT
Got SIGQUIT — exiting loop
22.10 — Synchronously Waiting: sigwaitinfo()
Sometimes you don’t want a signal handler at all. You just want to block until a signal arrives and then process it inline. sigwaitinfo() does exactly this.
sigwaitinfo() and sigtimedwait()
#define _POSIX_C_SOURCE 199309
#include <signal.h>
/* Block until a signal in 'set' arrives. Returns signal number.
If 'info' is not NULL, fills it with siginfo_t data. */
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
/* Same as sigwaitinfo but with a timeout */
int sigtimedwait(const sigset_t *set, siginfo_t *info,
const struct timespec *timeout);
/* Returns signal number, or -1 with errno=EAGAIN if timeout */
| Feature | sigsuspend() | sigwaitinfo() |
|---|---|---|
| Needs a signal handler? | YES — signal handler runs | NO — processes signal inline |
| Gets siginfo_t data? | Only if SA_SIGINFO handler | Always (if info != NULL) |
| Performance | Slower (context switch to handler) | Faster (no handler overhead) |
| Mask after signal? | Restored to pre-sigsuspend mask | Signal removed from pending set; mask unchanged |
| Timeout support? | NO | YES (sigtimedwait) |
/* sigwaitinfo_demo.c - Wait for signals without a handler */
#define _POSIX_C_SOURCE 199309
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
int main(void) {
/* Block the signals we want to receive synchronously */
sigset_t wait_set;
sigfillset(&wait_set); /* block everything */
sigprocmask(SIG_SETMASK, &wait_set, NULL);
printf("PID=%d: waiting for signals via sigwaitinfo()\n", getpid());
printf("Send signals with: kill -USR1 %d or kill -RTMIN %d\n\n",
getpid(), getpid());
while (1) {
siginfo_t si;
int sig = sigwaitinfo(&wait_set, &si);
if (sig == -1) {
perror("sigwaitinfo");
continue;
}
printf("Got signal: %d (%s)\n", sig, strsignal(sig));
printf(" si_code = %d (%s)\n", si.si_code,
(si.si_code == SI_USER) ? "SI_USER (from kill)" :
(si.si_code == SI_QUEUE) ? "SI_QUEUE (from sigqueue)" :
"other");
printf(" si_pid = %d\n", si.si_pid);
printf(" si_uid = %d\n", si.si_uid);
if (si.si_code == SI_QUEUE)
printf(" si_value.sival_int = %d\n", si.si_value.sival_int);
printf("\n");
if (sig == SIGTERM || sig == SIGINT) {
printf("Got termination signal, exiting.\n");
break;
}
}
return 0;
}
/* sigtimedwait_demo.c - sigwaitinfo with timeout (poll mode) */
#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
int main(void) {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
sigprocmask(SIG_BLOCK, &mask, NULL);
struct timespec timeout = { .tv_sec = 2, .tv_nsec = 0 }; /* 2 seconds */
printf("PID=%d: waiting up to 2 seconds for SIGUSR1\n", getpid());
while (1) {
siginfo_t si;
int sig = sigtimedwait(&mask, &si, &timeout);
if (sig == -1) {
if (errno == EAGAIN) {
printf("Timeout! No signal in 2 seconds. Checking other work...\n");
/* Do other work here while waiting */
continue;
}
perror("sigtimedwait");
break;
}
printf("Got SIGUSR1 from PID=%d\n", si.si_pid);
break;
}
return 0;
}
/* timeout.tv_sec=0, timeout.tv_nsec=0 → immediate poll: check if signal pending */
struct timespec zero_timeout = { 0, 0 };
int sig = sigtimedwait(&mask, &si, &zero_timeout);
if (sig == -1 && errno == EAGAIN)
printf("No signal currently pending\n");
else if (sig > 0)
printf("Signal %d was pending!\n", sig);
22.11 — Signals as File Descriptors: signalfd()
Linux-specific (since kernel 2.6.22): signalfd() creates a file descriptor from which you can read() signals. This is very useful for event-loop architectures where you want to monitor signals alongside sockets, pipes, and timers using select()/poll()/epoll().
signalfd() API and signalfd_siginfo Structure
#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t *mask, int flags);
/* fd=-1: create new fd; otherwise modify existing signalfd
flags: 0, SFD_CLOEXEC, SFD_NONBLOCK
Returns file descriptor on success, -1 on error */
/* Read from signalfd — each read() returns one signalfd_siginfo */
struct signalfd_siginfo {
uint32_t ssi_signo; /* Signal number */
int32_t ssi_errno; /* Errno (unused) */
int32_t ssi_code; /* Signal code (SI_USER, SI_QUEUE, etc.) */
uint32_t ssi_pid; /* Sender PID */
uint32_t ssi_uid; /* Sender real UID */
int32_t ssi_fd; /* File descriptor (SIGPOLL/SIGIO) */
uint32_t ssi_tid; /* Kernel timer ID */
uint32_t ssi_band; /* Band event */
uint32_t ssi_overrun; /* Overrun count (POSIX timers) */
int32_t ssi_status; /* Exit status (SIGCHLD) */
int32_t ssi_int; /* Integer sent by sigqueue() */
uint64_t ssi_ptr; /* Pointer sent by sigqueue() */
uint64_t ssi_utime; /* User CPU time (SIGCHLD) */
uint64_t ssi_stime; /* System CPU time (SIGCHLD) */
uint64_t ssi_addr; /* Faulting address (hardware signals) */
};
/* signalfd_basic.c - Read signals from a file descriptor */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/signalfd.h>
#include <string.h>
int main(void) {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGRTMIN);
/* MUST block the signals before creating signalfd,
otherwise signals are delivered normally and missed by signalfd */
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
perror("sigprocmask");
exit(1);
}
/* Create the signalfd file descriptor */
int sfd = signalfd(-1, &mask, SFD_CLOEXEC);
if (sfd == -1) {
perror("signalfd");
exit(1);
}
printf("PID=%d: Waiting for signals (SIGINT, SIGTERM, SIGUSR1, SIGRTMIN)\n",
getpid());
printf("Use: kill -INT %d or kill -USR1 %d\n\n", getpid(), getpid());
while (1) {
struct signalfd_siginfo fdsi;
ssize_t s = read(sfd, &fdsi, sizeof(fdsi));
if (s != sizeof(fdsi)) {
perror("read");
exit(1);
}
printf("Signal %d received:\n", fdsi.ssi_signo);
printf(" From PID: %u\n", fdsi.ssi_pid);
printf(" Code: %d (%s)\n", fdsi.ssi_code,
fdsi.ssi_code == SI_USER ? "SI_USER" :
fdsi.ssi_code == SI_QUEUE ? "SI_QUEUE" : "other");
if (fdsi.ssi_code == SI_QUEUE)
printf(" Data: %d\n", fdsi.ssi_int);
printf("\n");
if (fdsi.ssi_signo == SIGTERM || fdsi.ssi_signo == SIGINT) {
printf("Shutdown signal received, exiting.\n");
break;
}
}
close(sfd);
return 0;
}
/* signalfd_epoll.c - signalfd in an epoll event loop (production pattern) */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/signalfd.h>
#include <sys/epoll.h>
int main(void) {
/* Block signals we'll handle via signalfd */
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);
/* Create signalfd with SFD_NONBLOCK for use in event loop */
int sfd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK);
/* Create epoll instance */
int epfd = epoll_create1(EPOLL_CLOEXEC);
/* Add signalfd to epoll */
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev);
/* Also add stdin to epoll */
ev.events = EPOLLIN;
ev.data.fd = STDIN_FILENO;
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
printf("PID=%d: Event loop running. Press Enter or Ctrl+C\n", getpid());
struct epoll_event events[10];
while (1) {
int nfds = epoll_wait(epfd, events, 10, -1);
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == sfd) {
/* Signal ready to read */
struct signalfd_siginfo si;
read(sfd, &si, sizeof(si));
printf("Signal %d from PID %u\n", si.ssi_signo, si.ssi_pid);
if (si.ssi_signo == SIGINT || si.ssi_signo == SIGTERM) {
printf("Shutting down event loop\n");
close(sfd); close(epfd);
return 0;
}
} else if (events[i].data.fd == STDIN_FILENO) {
char buf[128];
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\0';
printf("stdin: %s", buf);
}
}
}
}
}
Interview Questions — sigsuspend, sigwaitinfo, signalfd
The race condition occurs when trying to: (1) unblock a signal with sigprocmask(), then (2) wait for it with pause(). If the signal arrives between step 1 and step 2, the handler runs before pause() is called. pause() then waits forever because the signal was already delivered. sigsuspend(mask) atomically performs both steps — there is no window between unblocking the signal and suspending. The kernel guarantees these happen together as a single indivisible operation.
sigsuspend() always returns -1 with errno set to EINTR (when a signal handler returns). It never returns 0. After returning, the process signal mask is automatically restored to what it was before the sigsuspend() call. This restoration is also atomic — the mask is restored before the function returns to the caller.
Use sigwaitinfo() when: (1) You don’t need a signal handler and want to process signals inline in your main code. (2) You need the siginfo_t data (sender PID, attached data) without the overhead of a handler. (3) Performance matters — sigwaitinfo() is faster than sigsuspend()+handler because there is no context switch to handler code. (4) You want timeout support (use sigtimedwait() variant). sigsuspend() is better when you already have signal handlers and just need to wait atomically.
signalfd() (Linux 2.6.22+) creates a file descriptor from which you can read() pending signals as signalfd_siginfo structures. The main advantage is integration with event loops: you can monitor signals alongside sockets, pipes, and timers using select(), poll(), or epoll(). This eliminates the need for the “self-pipe trick” and makes it easy to handle signals in single-threaded event-driven servers. SFD_NONBLOCK allows non-blocking reads; SFD_CLOEXEC prevents inheritance across exec().
You must block the signals you intend to receive via signalfd using sigprocmask(SIG_BLOCK, …) BEFORE calling signalfd(). If you don’t, signals may be delivered via the default/installed handler before signalfd has a chance to read them. Once blocked, signals are queued by the kernel until your code reads them from the signalfd. The rule is: block first, then create the signalfd for those same signals.
sigtimedwait() adds a timeout parameter (struct timespec). If no signal arrives within the timeout, it returns -1 with errno=EAGAIN. sigwaitinfo() blocks indefinitely. A zero timeout (tv_sec=0, tv_nsec=0) makes sigtimedwait() a non-blocking poll — it checks if any of the specified signals are currently pending and returns immediately either with the signal number or EAGAIN. This is useful for draining a signal queue without blocking.
Next Topic
Can signals be used for IPC? Learn the limitations.
