本页目录

io_uring: 革命性异步 IO

覆盖: SQ/CQ ring → fixed buffers → polled IO → 与 libaio/posix aio 对比 → 性能考量 适用: Linux 5.1+

概述

io_uring 是 Linux 5.1 引入的异步 IO 框架,彻底改变了 Linux IO 的玩法。它通过两个共享内存环形缓冲区(SQ/CQ)在用户态和内核之间传递 IO 请求和完成通知——不需要系统调用就能提交和收割 IO。在最优情况下,io_uring 可以达到 ~6M IOPS(单核),而传统 aio 只有 ~1M。

环形缓冲区模型

flowchart LR
    subgraph USER["用户态"]
        APP["App<br/>写 SQ / 读 CQ"]
    end

    subgraph SHARED["共享内存 (mmap, 零拷贝)"]
        SQ["Submission Queue<br/>IO 请求队列"]
        CQ["Completion Queue<br/>IO 完成队列"]
    end

    subgraph KERNEL["内核态"]
        K["Kernel<br/>读 SQ / 写 CQ"]
    end

    APP -->|"① 写入 SQE"| SQ
    SQ -->|"② 内核读取"| K
    K -->|"③ 写入 CQE"| CQ
    CQ -->|"④ App 收割"| APP

    classDef user fill:#e3f2fd,stroke:#1565c0
    classDef shared fill:#fff3e0,stroke:#ef6c00
    classDef kernel fill:#f3e5f5,stroke:#7b1fa2
    class APP user
    class SQ,CQ shared
    class K kernel

基本用法

#include <liburing.h>  // liburing helper library

struct io_uring ring;
io_uring_queue_init(256, &ring, 0);  // 256 entries in SQ

// 准备一个 read 请求:
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, size, offset);  // offset: fixed position
sqe->user_data = 42;  // 用户标签 (返回在 CQE 中)

// 提交请求:
io_uring_submit(&ring);

// 收割完成:
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);  // 阻塞等 1 个完成
printf("res=%d, user_data=%lu\n", cqe->res, cqe->user_data);
io_uring_cqe_seen(&ring, cqe);

高级特性

SQ Polling (内核侧忙等)

// io_uring_setup(..., IORING_SETUP_SQPOLL)
// 内核启动一个 kthread → 持续轮询 SQ → 有请求立即处理
// → 不需要 io_uring_submit() → 零系统调用!
// → io_uring_enter(ring_fd, 0, 0, IORING_ENTER_GETEVENTS, NULL)
//    只收割完成,不提交 (提交由 SQ poller 自动做)

// 代价: SQ poller 线程持续占 CPU (5-10% of one core)
// 适合: 持续有 IO 的场景 (数据库, 文件服务器)

Fixed Buffers / Fixed Files

// 预注册 buffer 和 fd:
io_uring_register_buffers(&ring, iovecs, nr_iovs);  // 固定 buffer
io_uring_register_files(&ring, fds, nr_fds);        // 固定文件

// 之后用 registered buffer → 避免每次 IO 的 get_user_pages
// 之后用 fixed fd → 避免每次 IO 的 fget/fput
// → 进一步降低延迟 (减少 ~30-50% 内核开销)

IORING_SETUP_DEFER_TASKRUN

// 默认: CQE → 内核在中断上下文中标记完成
// DEFER_TASKRUN: 内核延迟 I/O 完成处理 → 下次 io_uring_enter 时批量处理
// → 减少中断 + 批处理 → 更高吞吐

与 aio 对比

io_uringlibaioPOSIX aio
完成通知轮询 CQio_getevents()信号 / 回调
缓冲区预注册或每次指定每次指定每次指定
Buffered IO支持 (5.6+)不支持 (仅 O_DIRECT)支持
系统调用理论 0 (SQ poll)每次提交 + 每次收割基于线程池
性能 (4K randread)~6M IOPS~1M IOPS~0.1M IOPS

性能陷阱

1. CQE 收割: io_uring_wait_cqe 每次只收 1 个 → 用 io_uring_wait_cqes 收多个
2. SQ 满了: io_uring_get_sqe 返回 NULL → 先 io_uring_submit 再重试
3. Large SQ depth: 256-512 就够了 → 更大的 depth 只是浪费内存
4. link timeout: 给每个请求加超时 → IORING_OP_LINK_TIMEOUT
   → 防止慢 IO 阻塞整个 ring

参考

  • liburing: https://github.com/axboe/liburing (examples + tests)
  • 内核源码⁠: fs/io_uring.c (~20000行)
  • LWN: "The io_uring API", "io_uring performance improvements"

关键词: io_uring, SQ, CQ, SQE, CQE, fixed buffers, SQPOLL, DEFER_TASKRUN, libaio