In Part 1 we saw that using F_SETSIG with a realtime signal solves the signal-loss problem โ realtime signals are queued. But the queue is not infinite. The Linux kernel limits how many realtime signals can be pending for a process at one time.
When that limit is hit and a new I/O event arrives, the kernel cannot queue another realtime signal. Instead it falls back to delivering the default SIGIO. This is the overflow notification โ it tells your program “something happened but I lost track of exactly what.”
If your application does not handle this case, it will silently miss I/O events under high load.
There is a per-user limit on the number of queued realtime signals. You can check it at:
# Check the realtime signal queue limit for your user
cat /proc/sys/kernel/rtsig-max # older kernels
ulimit -i # RLIMIT_SIGPENDING (current user)
# Check current pending count (Linux specific)
cat /proc/$(pgrep your_process)/status | grep SigQ
When the limit is reached:
We know overflow happened but don’t know which FD or event type
This is a serious problem: the SIGIO fallback handler cannot determine which file descriptors have pending I/O. The only option is to drain the realtime signal queue and then use select() or poll() to figure out what is ready.
A correctly written application using F_SETSIG must install a separate SIGIO handler as a fallback. The strategy is:
- Drain all queued realtime signals using
sigwaitinfo()(non-blocking loop) - Temporarily use
select()orpoll()to get a complete picture - Process all ready FDs, then resume normal realtime-signal monitoring
#define _GNU_SOURCE
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#define MAX_FDS 64
/* Global list of monitored file descriptors */
static int watched_fds[MAX_FDS];
static int nfds = 0;
/* Called when the realtime signal queue overflows and SIGIO is delivered */
static void sigio_overflow_handler(int sig)
{
sigset_t rt_mask;
siginfo_t si;
struct timespec zero = {0, 0};
printf("WARNING: realtime signal queue overflowed! Falling back to poll()\n");
/* Step 1: Drain whatever is left in the realtime signal queue */
sigemptyset(&rt_mask);
sigaddset(&rt_mask, SIGRTMIN);
while (sigtimedwait(&rt_mask, &si, &zero) > 0)
; /* discard โ we will use poll() to get fresh info */
/* Step 2: Use poll() to find ALL ready file descriptors */
struct pollfd pfds[MAX_FDS];
for (int i = 0; i < nfds; i++) {
pfds[i].fd = watched_fds[i];
pfds[i].events = POLLIN | POLLOUT;
pfds[i].revents = 0;
}
int ready = poll(pfds, nfds, 0); /* timeout=0: non-blocking */
if (ready <= 0)
return;
/* Step 3: Process each ready descriptor */
for (int i = 0; i < nfds; i++) {
if (pfds[i].revents & POLLIN)
printf(" fd %d is readable\n", pfds[i].fd);
if (pfds[i].revents & POLLOUT)
printf(" fd %d is writable\n", pfds[i].fd);
if (pfds[i].revents & (POLLERR | POLLHUP))
printf(" fd %d has error/hangup\n", pfds[i].fd);
}
}
/* Normal realtime signal handler for I/O events */
static void rt_io_handler(int sig, siginfo_t *si, void *ctx)
{
printf("RT signal: fd=%d si_code=%d\n", si->si_fd, si->si_code);
}
int main(void)
{
struct sigaction sa;
/* Install SIGIO handler for overflow recovery */
sa.sa_handler = sigio_overflow_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGIO, &sa, NULL);
/* Install realtime signal handler with SA_SIGINFO */
sa.sa_sigaction = rt_io_handler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGRTMIN, &sa, NULL);
/* Watch stdin */
watched_fds[0] = STDIN_FILENO;
nfds = 1;
/* Setup async I/O */
fcntl(STDIN_FILENO, F_SETOWN, getpid());
int fl = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, fl | O_ASYNC);
fcntl(STDIN_FILENO, F_SETSIG, SIGRTMIN);
printf("Monitoring stdin. Type to trigger I/O events.\n");
for (;;)
pause();
return 0;
}
You can reduce the chance of overflow by raising the limit, but you cannot eliminate the need for overflow handling โ the limit always exists.
# Check current limit (signals pending allowed per user)
ulimit -i
# Temporarily raise to 8192 for this shell session
ulimit -i 8192
# Permanently raise via /etc/security/limits.conf
# Add this line:
# yourusername soft sigpending 8192
# yourusername hard sigpending 8192
# Check via /proc for a running process
cat /proc/$(pgrep myapp)/status | grep SigQ
# Output example:
# SigQ: 23/7637 (23 pending out of max 7637)
โ ๏ธ Raising the limit is useful but not enough on its own. Always write the SIGIO fallback handler. Under extreme load (many monitored FDs with bursts of events), overflow can still happen.
/proc/PID/status contains a SigQ field showing “current_queued/max_allowed” โ for example “23/7637”. You can also check the limit with ulimit -i.Learn F_SETOWN_EX and how to direct I/O signals to a specific thread.
