Back to articles
Networking

io_uring: The Future of Async I/O

Deep dive into Linux's revolutionary io_uring interface and how it outperforms traditional async I/O.

kos1
January 3, 2026
14 min read

Introduction

io_uring is Linux's modern async I/O interface, introduced in kernel 5.1. It provides a fundamentally different approach to I/O: instead of making system calls for each operation, you submit work to shared ring buffers.

The Problem with Traditional I/O

Traditional async I/O in Linux has significant overhead:

  • epoll - Requires separate read/write syscalls after notification
  • aio - Limited to direct I/O, poor socket support
  • Syscall overhead - Context switches for every operation

How io_uring Works

io_uring uses two ring buffers shared between userspace and kernel:

  • Submission Queue (SQ) - You push I/O requests here
  • Completion Queue (CQ) - Kernel pushes results here
// Simplified flow
1. Prepare SQE (Submission Queue Entry)
2. Submit to kernel (or let kernel poll)
3. Kernel processes I/O asynchronously
4. Read CQE (Completion Queue Entry) for result

Basic Setup with liburing

#include 

struct io_uring ring;

// Initialize with 256 entries
io_uring_queue_init(256, &ring, 0);

// Get a submission queue entry
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);

// Prepare a read operation
io_uring_prep_read(sqe, fd, buffer, size, offset);

// Set user data for identifying completions
io_uring_sqe_set_data(sqe, my_context);

// Submit to kernel
io_uring_submit(&ring);

// Wait for completion
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);

// Process result
int result = cqe->res;
void *ctx = io_uring_cqe_get_data(cqe);

// Mark as seen
io_uring_cqe_seen(&ring, cqe);

Advanced Features

Registered Buffers

Pre-register buffers to avoid repeated memory mapping:

struct iovec iovecs[NUM_BUFFERS];
// ... initialize iovecs ...
io_uring_register_buffers(&ring, iovecs, NUM_BUFFERS);

// Use with IORING_OP_READ_FIXED
io_uring_prep_read_fixed(sqe, fd, buf, size, offset, buf_index);

Linked Operations

Chain operations so the next starts only if the previous succeeds:

sqe1->flags |= IOSQE_IO_LINK;
// sqe2 only executes if sqe1 succeeds

Kernel-side Polling

Let the kernel poll for completions without syscalls:

io_uring_queue_init(256, &ring, IORING_SETUP_SQPOLL);

Performance Results

In benchmarks, io_uring consistently outperforms epoll:

  • 50-80% higher throughput for small I/O operations
  • Significantly lower CPU usage due to batching
  • Better latency distribution under load

Conclusion

io_uring represents the future of Linux I/O. Its zero-copy, batched design eliminates the syscall overhead that has limited async I/O performance for decades.

Tags

#linux#io_uring#async#networking