Skip to content

Event-Driven I/O in Linux

Overview

In Linux, device drivers do not provide callback-based async APIs to user space.

Instead, Linux uses a unified I/O model:

  • blocking I/O
  • non-blocking I/O
  • event-driven I/O through poll/select/epoll

Blocking vs Non-Blocking

Blocking read

read(fd, buf, ...);
  • If no data is available, the process sleeps.
  • The driver wakes it up later through a wait queue.

Non-blocking read

open("/dev/mygpio", O_RDONLY | O_NONBLOCK);
read(fd, buf, ...);
  • If no data is available, read() returns immediately.
  • The usual return code is -EAGAIN.

Event-Driven I/O

Key idea

Event-driven I/O separates:

  • waiting for an event
  • reading the data

Typical user-space flow:

poll(...);
read(...);

poll() waits for readiness.
read() fetches the actual data.


Wait Queue

Purpose

A wait queue is used to:

  • put a process to sleep
  • wake it up later when an event occurs

Declaration

wait_queue_head_t read_queue;

Initialization

init_waitqueue_head(&mydev->read_queue);

Sleep

wait_event_interruptible(mydev->read_queue,
                         mydev->event_pending != 0);

This puts the process to sleep until the condition becomes true.


Wakeup

wake_up_interruptible(&mydev->read_queue);

This wakes processes sleeping on the wait queue.


poll()

Role

poll() does not read data.

It only answers this question:

  • Is this file descriptor ready?

Driver implementation example

static __poll_t mygpio_poll(struct file *file, poll_table *wait)
{
    struct mygpio_dev *mydev = file->private_data;
    __poll_t mask = 0;

    poll_wait(file, &mydev->read_queue, wait);

    if (mydev->event_pending)
        mask |= POLLIN | POLLRDNORM;

    return mask;
}

Key API: poll_wait()

poll_wait(file, &mydev->read_queue, wait);

This connects the file descriptor to the driver's wait queue.

If no event is ready yet, the process can sleep and later be awakened by:

wake_up_interruptible(&mydev->read_queue);

Return value

  • POLLIN means readable
  • POLLRDNORM means normal readable data
  • 0 means not ready

Relationship Between Components

IRQ
 → event_pending = 1
 → wake_up_interruptible()
 → poll() wakes up
 → read() consumes data
 → event_pending = 0

Driver vs User Space Responsibilities

Driver side

The driver is responsible for:

  • detecting the event
  • storing event state
  • waking sleeping processes
  • reporting readiness through .poll()

User-space side

User space is responsible for:

  • choosing blocking or non-blocking mode
  • calling poll/select/epoll
  • calling read() to fetch data

Important Insight

Linux does not let the kernel call user callbacks directly.

Instead:

  • the kernel exposes readiness
  • user space controls its own event loop

Summary

Feature Mechanism
Blocking read wait_event_interruptible()
Non-blocking read O_NONBLOCK + -EAGAIN
Event-driven I/O poll/select/epoll
Wakeup source IRQ
Bridge between IRQ and user wait queue