Free — no signup required

Generation 3: io_uring (The New King)

2 min read

The Problem with Epoll

epoll solves the polling problem, but it does not eliminate system calls. Every read(), write(), send(), and recv() is still a system call — a deliberate crossing of the boundary between user space and kernel space. This crossing involves:

  1. Saving CPU registers.
  2. Switching the CPU privilege level (ring 3 → ring 0).
  3. Validating the system call arguments.
  4. Executing the kernel code.
  5. Switching back (ring 0 → ring 3).
  6. Restoring registers.

On a modern CPU, a system call costs roughly 100–1,000 nanoseconds. For a web server doing 100,000 requests/second, that is 100ms of pure system call overhead per second — 10% of a CPU core doing nothing useful. For NVMe storage capable of 1,000,000 IOPS, the system call overhead can exceed the actual I/O time.

Enter io_uring (Linux 5.1, 2019)

io_uring was written by Jens Axboe (the same engineer who wrote the Linux block layer) to solve this problem. The core idea: share memory between user space and kernel space so that submitting and completing I/O requires no system calls at all.

io_uring sets up two ring buffers in shared memory:

  • Submission Queue (SQ): The application writes I/O requests (read, write, fsync, accept, etc.) into this ring buffer by advancing a tail pointer.
  • Completion Queue (CQ): The kernel reads requests from the SQ, executes them, and writes results into this ring buffer by advancing a tail pointer.
User Space                    Kernel Space
┌─────────────────┐           ┌─────────────────┐
│ Submission Queue│◄──shared──►│ Submission Queue│
│ [req][req][   ] │  memory   │ (kernel reads)  │
└─────────────────┘           └─────────────────┘
┌─────────────────┐           ┌─────────────────┐
│ Completion Queue│◄──shared──►│ Completion Queue│
│ [res][   ][   ] │  memory   │ (kernel writes) │
└─────────────────┘           └─────────────────┘

In polling mode (IORING_SETUP_SQPOLL), a dedicated kernel thread continuously polls the SQ for new requests. The application never needs to make a system call — it just writes to the SQ and reads from the CQ.

What io_uring Can Do

io_uring is not just for disk I/O. It supports:
- File reads and writes (readv, writev)
- Network operations (accept, send, recv)
- fsync, fallocate, splice
- Linked requests (chain operations so B only runs after A completes)
- Fixed buffers (pre-registered memory to avoid per-call mapping overhead)

Real-world adoption: io_uring is used in Cloudflare's network stack, RocksDB, liburing (a user-friendly wrapper library), and is being adopted in PostgreSQL and the Linux kernel's own internal I/O paths.

Security note: io_uring has had several CVEs due to its complexity and the power it grants to user space. Some container runtimes (Docker, gVisor) disable it by default via seccomp filters. This is a real operational consideration when running untrusted workloads.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free