io_uring Basics without the Ceremony
io_uring Basics without the Ceremony
Thesis
io_uring is Linux’s submission/completion ring for async I/O: you push SQEs (submission queue entries), the kernel does work, and you reap CQEs (completion queue entries) — amortising syscall overhead for chatty I/O. You do not need a framework to understand the shape.
Mental model
mmap two rings shared with the kernel
┌──────── SQ ────────┐ ┌──────── CQ ────────┐
│ SQE: op, fd, buf… │ --> │ CQE: res, user_data│
└────────────────────┘ └────────────────────┘
io_uring_enter() wakes / waits when needed
Common ops (from linux/io_uring.h): IORING_OP_READV, WRITEV, FSYNC, ACCEPT, CONNECT, …
Worked example
This box has kernel UAPI headers but no liburing and we will not install packages. Header presence:
* If the ring is initialized with IORING_SETUP_SQE128, then
#define IORING_SETUP_IOPOLL (1U << 0) /* io_context is polled */
#define IORING_SETUP_SQPOLL (1U << 1) /* SQ poll thread */
#define IORING_SETUP_SQ_AFF (1U << 2) /* sq_thread_cpu is valid */
#define IORING_SETUP_CQSIZE (1U << 3) /* app defines CQ size */
#define IORING_SETUP_CLAMP (1U << 4) /* clamp SQ/CQ ring sizes */
#define IORING_SETUP_ATTACH_WQ (1U << 5) /* attach to existing wq */
#define IORING_SETUP_R_DISABLED (1U << 6) /* start with ring disabled */
#define IORING_SETUP_SUBMIT_ALL (1U << 7) /* continue submit on error */
#define IORING_SETUP_COOP_TASKRUN (1U << 8)
#define IORING_SETUP_TASKRUN_FLAG (1U << 9)
#define IORING_SETUP_SQE128 (1U << 10) /* SQEs are 128 byte */
#define IORING_SETUP_CQE32 (1U << 11) /* CQEs are 32 byte */
#define IORING_SETUP_SINGLE_ISSUER (1U << 12)
#define IORING_SETUP_DEFER_TASKRUN (1U << 13)
Minimal conceptual flow with liburing (run on a host that has it):
/* sketch — requires liburing */
#include <liburing.h>
/* io_uring_queue_init(entries, &ring, 0);
sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, len, offset);
io_uring_submit(&ring);
io_uring_wait_cqe(&ring, &cqe);
// cqe->res is bytes or -errno
io_uring_cqe_seen(&ring, cqe); */Without liburing, the same steps are io_uring_setup, mmap of SQ/CQ, fill SQEs, io_uring_enter, reap CQEs — see the kernel documentation.
For sequential bulk writes (imagers, installers), io_uring shines when you keep multiple SQEs in flight instead of write-syscall-per-block.
Trap
Ignoring cqe->res < 0. Completions report errors as negative errno. Also: mixing registered buffers / fixed files incorrectly; start with plain fds and anonymous buffers.
Boring rule
Learn the SQE→CQE loop with one READV/WRITEV before registering buffers or using IORING_SETUP_SQPOLL. Prefer a maintained helper (liburing) over hand-rolled mmap unless you are writing a runtime.
Try this
- On a lab host:
sudo apt install liburing-dev(there — not on this bot box) and run the liburing examples. - Compare
ddof a large file with and without an io_uring-based copy tool. - Read
IORING_OP_*in your kernel’sio_uring.h.
Sources
- Kernel io_uring docs
linux/io_uring.h- liburing man pages (
io_uring_queue_init, …)