Chapter 55B: Async notification (SIGIO)¶
IRQ: interrupt request, the signal path that tells the CPU or interrupt controller that hardware needs service. MCU bridge: Think of an IRQ like an EXTI/NVIC interrupt path, except Linux splits the hard interrupt from deferred work and must share lines across drivers.
What: the fasync / SIGIO mechanism, a driver tells its user-space process “data is ready” by sending it a Unix signal, instead of the process polling or blocking on read. The driver implements
.fasyncinfile_operations. User-space arms it withfcntl(fd, F_SETOWN, getpid()); fcntl(fd, F_SETFL, O_ASYNC);.Why: for very rare events (“button pressed” or “thermal alarm”), forcing user-space to
poll()continuously or block onread()is wasteful. SIGIO lets the application do other things. The signal arrives when something happens. Linux’s input layer doesn’t use it (poll/epoll is preferred), but legacy POSIX-style apps and some custom devices do.Focus: registers a process for delivery. Driver triggers. The driver maintains a
fasync_struct. When an event happens, it callskill_fasyncand the kernel sends the registered process a SIGIO.
55B.1 Driver side¶
Add .fasync to your file_operations:
#include <linux/fs.h>
struct my_priv {
struct fasync_struct *fasync;
/* ... */
};
static int my_fasync(int fd, struct file *filp, int mode)
{
struct my_priv *p = filp->private_data;
return fasync_helper(fd, filp, mode, &p->fasync);
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
/* ... */
.fasync = my_fasync,
};
/* When an event occurs (e.g., IRQ → data ready): */
static void notify_userspace(struct my_priv *p)
{
if (p->fasync)
kill_fasync(&p->fasync, SIGIO, POLL_IN);
}
/* In release(): */
static int my_release(struct inode *inode, struct file *filp)
{
my_fasync(-1, filp, 0); /* clean up the fasync registration */
return 0;
}
fasync_helper(fd, filp, mode, &list) manages the list of registered processes. kill_fasync(&list, signal, band) sends the signal to every registered process.
55B.2 User-space side¶
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
static volatile sig_atomic_t got_event;
static void on_sigio(int sig)
{
got_event = 1;
}
int main(void)
{
int fd = open("/dev/myevent", O_RDONLY);
/* Register our signal handler */
struct sigaction sa = {0};
sa.sa_handler = on_sigio;
sigaction(SIGIO, &sa, NULL);
/* Tell the kernel: send signals to *this* process */
fcntl(fd, F_SETOWN, getpid());
/* Enable async notification */
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_ASYNC);
while (1) {
pause(); /* sleep until any signal arrives */
if (got_event) {
char buf[64];
int n = read(fd, buf, sizeof(buf) - 1);
buf[n] = 0;
printf("event: %s\n", buf);
got_event = 0;
}
}
}
Three steps. First, install the signal handler. Second, F_SETOWN tells the kernel which process gets the signal. Third, F_SETFL | O_ASYNC enables delivery.
55B.3 When to use SIGIO (and when not to)¶
Use when:
The event is rare (≤ a few per second).
The application is already signal-handling (legacy code).
Polling overhead would be wasteful.
Don’t use when:
Events come at > ~100 Hz (signal delivery is expensive).
You can use
poll()/select()/epollinstead, those are more efficient and don’t have signal-handler restrictions.You need to know which fd fired (SIGIO carries the band but not the fd unless you use
F_SETSIGfor realtime signals withsiginfo).
For modern code, poll() / epoll are preferred for everything. SIGIO is worth knowing about, but rarely the right tool today.
55B.4 Key Notes¶
SIGIO is a notification that device state may need attention, not the data itself. The application’s queue or read interface remains the source of events.
Signal ownership, enabling asynchronous delivery and device release form one registration lifecycle. Stale registrations can outlive the intended recipient.
Ordinary signals can merge. A recipient must drain available work rather than assume one signal corresponds to one hardware event.
Signal-handler context restricts what the application can safely do. Complex processing belongs outside the handler.
poll()andepoll()also provide event-driven waiting without continuous busy polling. The choice is an application interface decision, not a requirement to use signals for efficiency.
55B.5 Pitfalls¶
Signal handler doing too much. Signal handlers run in arbitrary context. Only async-signal-safe functions allowed. Set a flag. Do real work in the main loop.
Forgetting
F_SETOWN. Without it, the kernel doesn’t know who to signal.Race: signal arrives before sigaction installed. Install handler before opening the device.
Signal merging. If your driver fires SIGIO twice before user-space handles either, only one delivery happens. User-space must drain everything that was queued, not just handle one event.
fasync_helper(-1, ...)not called in release. Stale fasync entry. Later signals go to a dead pid.
55B.6 Going deeper¶
The relevant kernel docs are sparse, see LDD3 Chapter 6 and
man 2 fcntlinstead.drivers/char/: many older char drivers use SIGIO.kernel/signal.c,fs/fcntl.c, implementation.
Next chapter: Chapter 55C: CAN + FlexCAN. SocketCAN, kernel framework, FlexCAN driver, transceivers.