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 Lab¶
Add SIGIO to the button driver from Ch 45. Confirm a user-space process receives SIGIO when the button is pressed.
Compare against poll(). Write two equivalent programs, one with SIGIO, one with
poll(). Profile CPU and latency.F_SETSIG for SI_FD. Use a realtime signal (
SIGRTMIN+1) withF_SETSIG. Receivesiginfo->si_fdto know which fd fired.
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.