Signal-Driven I/O โ€“ F_SETSIG & Realtime Signals

Signal-Driven I/O โ€“ F_SETSIG & Realtime Signals
Chapter 63 โ€“ Alternative I/O Models | Part 1 of 5
๐Ÿ“ก Topic: Signal-Driven I/O
๐Ÿ”‘ Key: F_SETSIG, siginfo_t
๐ŸŽฏ Level: Intermediate

What is Signal-Driven I/O?

Normally when you want to know if a file descriptor is ready for reading or writing, you call select() or poll() and your program blocks waiting for an event. Signal-driven I/O flips this idea โ€” instead of your program waiting, the kernel sends you a signal the moment a file descriptor becomes ready.

By default the kernel sends SIGIO for this. But SIGIO has a serious problem: it is a standard non-queuing signal. If two I/O events happen at almost the same time, only one SIGIO is delivered and the second is silently lost.

The solution is F_SETSIG โ€” a fcntl() command that lets you replace SIGIO with a realtime signal. Realtime signals are queued, so no event is lost.

Keywords in this tutorial:

F_SETSIG F_GETSIG SIGIO Realtime Signal siginfo_t SA_SIGINFO si_fd si_code si_band _GNU_SOURCE sigwaitinfo() sigtimedwait()

๐Ÿšจ The Problem with Default SIGIO

When you enable signal-driven I/O using fcntl(fd, F_SETFL, O_ASYNC), the kernel notifies you of I/O events via SIGIO. But SIGIO is a standard signal โ€” it is NOT queued. Here is what happens when two events arrive quickly:

๐Ÿ“Š Standard SIGIO โ€” Signal Loss Problem
Event 1 arrives
SIGIO sent โœ…
โฌ‡
Handler running
SIGIO is blocked
โฌ‡
Event 2 arrives
SIGIO LOST โŒ
With Realtime Signal
โฌ‡
Event 1 queued โœ…
โฌ‡
Event 2 queued โœ…
Both delivered!

๐Ÿ”ง Using F_SETSIG to Switch to Realtime Signal

F_SETSIG is a fcntl() command that replaces the default SIGIO signal with any signal you choose โ€” typically a realtime signal like SIGRTMIN.

โš ๏ธ You must define _GNU_SOURCE before including <fcntl.h> to get the F_SETSIG and F_GETSIG constants.

#define _GNU_SOURCE       /* Required for F_SETSIG, F_GETSIG */
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>

/* Step 1: Set the owner (this process) to receive the signal */
fcntl(fd, F_SETOWN, getpid());

/* Step 2: Enable async (signal-driven) mode on the fd */
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_ASYNC);

/* Step 3: Replace SIGIO with a realtime signal */
fcntl(fd, F_SETSIG, SIGRTMIN);

/* To read back which signal is configured */
int sig = fcntl(fd, F_GETSIG, 0);
printf("Configured signal: %d\n", sig);

/* To go back to default SIGIO behavior, pass 0 */
fcntl(fd, F_SETSIG, 0);   /* reverts to SIGIO */

Once you set a realtime signal with F_SETSIG, the kernel queues each I/O notification. No event is dropped even if your handler is busy processing a previous one.

๐Ÿ“ฆ Getting Event Details via siginfo_t and SA_SIGINFO

There is a second advantage of F_SETSIG beyond queuing: when you install the handler using sigaction() with the SA_SIGINFO flag, the kernel passes a siginfo_t structure to your handler. This tells you:

  • Which file descriptor triggered the event (si_fd)
  • What type of event it was (si_code / si_band)

โš ๏ธ Both F_SETSIG AND SA_SIGINFO must be used together for siginfo_t to be populated correctly.

siginfo_t Fields for I/O Events
Field Meaning
si_signo Signal number that triggered the handler (same as first handler arg)
si_fd The file descriptor on which the I/O event occurred
si_code Type of I/O event (POLL_IN, POLL_OUT, POLL_ERR, etc.)
si_band Bitmask matching poll() revents (POLLIN, POLLOUT, POLLERR, etc.)

si_code and si_band Values
si_code si_band value Meaning
POLL_IN POLLIN | POLLRDNORM Input data available or end-of-file
POLL_OUT POLLOUT | POLLWRNORM | POLLWRBAND Output buffer has space โ€” can write
POLL_MSG POLLIN | POLLRDNORM | POLLMSG Input message available (rarely used)
POLL_ERR POLLERR I/O error on the file descriptor
POLL_PRI POLLPRI | POLLRDNORM High-priority input (e.g. TCP OOB data)
POLL_HUP POLLHUP | POLLERR Hangup โ€” peer closed connection

๐Ÿ’ป Complete Code Example โ€” F_SETSIG with SA_SIGINFO
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

/* Signal handler โ€” receives siginfo_t when SA_SIGINFO is used */
static void io_handler(int sig, siginfo_t *si, void *ucontext)
{
    printf("Signal %d received\n", sig);
    printf("  fd that triggered event : %d\n", si->si_fd);

    /* si_code tells us what happened */
    switch (si->si_code) {
    case POLL_IN:
        printf("  Event: data available to read (POLL_IN)\n");
        break;
    case POLL_OUT:
        printf("  Event: ready to write (POLL_OUT)\n");
        break;
    case POLL_HUP:
        printf("  Event: hangup (POLL_HUP)\n");
        break;
    case POLL_ERR:
        printf("  Event: I/O error (POLL_ERR)\n");
        break;
    default:
        printf("  Event: si_code=%d  si_band=%ld\n",
               si->si_code, si->si_band);
        break;
    }
}

int setup_signal_driven_io(int fd)
{
    struct sigaction sa;

    /* Install handler with SA_SIGINFO so siginfo_t is populated */
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = io_handler;     /* use sa_sigaction, not sa_handler */
    sa.sa_flags     = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
        perror("sigaction");
        return -1;
    }

    /* This process receives the signal */
    if (fcntl(fd, F_SETOWN, getpid()) == -1) {
        perror("F_SETOWN");
        return -1;
    }

    /* Enable async / signal-driven mode */
    int flags = fcntl(fd, F_GETFL);
    if (fcntl(fd, F_SETFL, flags | O_ASYNC) == -1) {
        perror("F_SETFL O_ASYNC");
        return -1;
    }

    /* Replace SIGIO with realtime signal SIGRTMIN */
    if (fcntl(fd, F_SETSIG, SIGRTMIN) == -1) {
        perror("F_SETSIG");
        return -1;
    }

    return 0;
}

int main(void)
{
    /* Use STDIN (fd=0) as an example */
    if (setup_signal_driven_io(STDIN_FILENO) == -1)
        return 1;

    printf("Waiting for I/O events on stdin. Type something and press Enter.\n");
    printf("Press Ctrl+C to quit.\n");

    /* Main loop โ€” do other work here; I/O events arrive as signals */
    for (;;) {
        pause();   /* sleep until a signal arrives */
    }

    return 0;
}

How to compile and test:

gcc -o sig_io sig_io.c
./sig_io
# In another terminal: echo "hello" > /proc/$(pgrep sig_io)/fd/0
# Or just type in the same terminal

โณ Synchronous Alternative โ€” sigwaitinfo() / sigtimedwait()

Instead of using an async signal handler, you can block the realtime signal and call sigwaitinfo() to synchronously wait for the next queued event. This gives you all the information of siginfo_t without writing an async-signal-safe handler.

#define _GNU_SOURCE
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    int fd = STDIN_FILENO;
    sigset_t mask;

    /* Block SIGRTMIN so it queues instead of being handled */
    sigemptyset(&mask);
    sigaddset(&mask, SIGRTMIN);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    /* Setup async I/O on fd */
    fcntl(fd, F_SETOWN, getpid());
    int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | O_ASYNC);
    fcntl(fd, F_SETSIG, SIGRTMIN);

    printf("Synchronously waiting for I/O...\n");

    for (;;) {
        siginfo_t si;

        /* Block here until SIGRTMIN arrives โ€” returns siginfo_t */
        int sig = sigwaitinfo(&mask, &si);
        if (sig == -1) {
            perror("sigwaitinfo");
            break;
        }

        printf("I/O event on fd %d, si_code=%d\n", si.si_fd, si.si_code);

        if (si.si_code == POLL_IN) {
            char buf[256];
            ssize_t n = read(si.si_fd, buf, sizeof(buf) - 1);
            if (n > 0) {
                buf[n] = '\0';
                printf("Read: %s", buf);
            }
        }
    }

    return 0;
}

This approach is cleaner โ€” no signal handler needed. The program still responds to I/O events efficiently but runs in a predictable sequential flow.

๐ŸŽฏ Interview Questions
Q1. What is the main problem with using SIGIO for signal-driven I/O when monitoring many file descriptors?
SIGIO is a standard non-queuing signal. If multiple I/O events occur while the SIGIO handler is already executing (SIGIO is blocked), all additional notifications except the first are silently lost. This makes SIGIO unreliable when monitoring many FDs.
Q2. What does F_SETSIG do and why is it better than the default SIGIO?
F_SETSIG is a fcntl() command that replaces the SIGIO notification with a realtime signal. Realtime signals are queued (multiple instances can be pending), so no I/O notifications are lost. Additionally, when combined with SA_SIGINFO, a siginfo_t structure is passed to the handler identifying exactly which fd triggered the event.
Q3. What two things must you do together to receive a populated siginfo_t in a signal handler for I/O events?
(1) Use fcntl(fd, F_SETSIG, realtime_signal) to set a realtime signal for I/O notifications. (2) Install the handler using sigaction() with the SA_SIGINFO flag set in sa_flags. Both are required together.
Q4. What does si_fd and si_code tell you in the siginfo_t received in an I/O signal handler?
si_fd is the file descriptor on which the I/O event occurred. si_code indicates the type of event โ€” e.g. POLL_IN (data available), POLL_OUT (can write), POLL_HUP (hangup), POLL_ERR (error). This lets one signal handler manage many file descriptors without doing extra select/poll calls.
Q5. What is the advantage of using sigwaitinfo() instead of a signal handler for signal-driven I/O?
Signal handlers must follow async-signal-safety rules (very restrictive โ€” most library functions cannot be called). By blocking the realtime signal with sigprocmask() and calling sigwaitinfo() in the main loop, you get the same event information through siginfo_t but in a normal synchronous context where any function can be called safely.
Q6. What feature test macro must be defined to use F_SETSIG and F_GETSIG?
_GNU_SOURCE must be defined before including <fcntl.h>. These constants are GNU/Linux extensions not in POSIX.

Next: Signal Queue Overflow Handling โ†’

Learn what happens when too many realtime signals pile up and how to handle it safely.

Part 2: Signal Queue Overflow EmbeddedPathashala Home

Leave a Reply

Your email address will not be published. Required fields are marked *