io_uring 的原理

原始问题:io_uring 的原理是啥 · 2026-06-30

io_uring 原理深度解析

📋 问题定位

🎯 核心设计理念:共享内存 + 无锁环形队列

io_uring 最革命性的想法是:通过内核与应用共享的环形缓冲区来传递 IO 请求和完成事件,将传统的 syscall → 内核处理 → 返回 模式,变成 应用写入 SQ → 内核消费 → 内核写入 CQ → 应用消费 的提交队列(SQ)与完成队列(CQ)双环模式。

这使得绝大多数场景下无需任何系统调用即可发起 IO——这才是性能提升的根本来源。

🏗️ 关键数据结构

1. 三个核心共享区域

用户态                                         内核态
═══════════════════════════════   ═══════════════════════════════
   ┌──────────────────────┐         ┌──────────────────────┐
   │    SQ (Submission     │ ◄────► │   sq_ring → sqes     │
   │     Queue Entries)    │   mmap │   (环形数组)          │
   ├──────────────────────┤         ├──────────────────────┤
   │    CQ (Completion     │ ◄────► │   cq_ring → cqes     │
   │     Queue Entries)    │   mmap │   (环形数组)          │
   ├──────────────────────┤         ├──────────────────────┤
   │    SQ 数组 (SQEs)     │ ◄────► │   内存中实际 SQE      │
   └──────────────────────┘         └──────────────────────┘
结构 含义 作用
SQ Submission Queue 应用写入要发起的 IO 请求(索引指向 SQE)
SQE Submission Queue Entry 实际的 IO 请求描述(struct io_uring_sqe,包含 fd/op/offset/flag 等)
CQ Completion Queue 内核写入已完成的 IO 结果
CQE Completion Queue Entry 完成结果描述(struct io_uring_cqe,包含 res/user_data)

2. SQE 结构体(liburing 头文件中定义)

struct io_uring_sqe {
    __u8    opcode;      /* IORING_OP_READV/WRITEV/ACCEPT/... */
    __u8    flags;       /* IOSQE_* flags */
    __u16   ioprio;      /* I/O priority */
    __s32   fd;          /* file descriptor */
    __u64   off;         /* offset in file */
    __u64   addr;        /* iovec/buffer address */
    __u32   len;         /* iovec count / buffer len */
    union {
        __kernel_rwf_t  rw_flags;
        ...
    };
    __u64   user_data;   /* 用户自定义数据,返回时带回 */
    __u16   buf_index;   /* buffer group index (for provided buffers) */
    ...
};

3. CQE 结构体

struct io_uring_cqe {
    __u64   user_data;   /* 对应 SQE 中填的 user_data */
    __s32   res;         /* 返回结果(类似 read/write 返回值或 errno) */
    __u32   flags;
};

🔄 IO 请求的完整生命周期

以一次 preadv2 为例:

应用线程                             内核 IO 线程 (or 系统调用路径)
═══════════                        ════════════════════════

1. 获取 SQE 槽位
   sqe = io_uring_get_sqe(ring);

2. 填充 SQE
   io_uring_prep_readv(sqe, fd, iov, iovcnt, offset);
   sqe->user_data = MY_TAG_123;

3. 通知内核有新请求           ──►   4. 内核从 SQ 消费请求
   io_uring_submit(ring);            io_submit_sqes()
   (实际更新 SQ tail,               ① 解析 SQE → 创建
    通过 smp_store_release                kiocb/io_kiocb
    写 tail 指针)                    ② 调用对应操作回调
                                        → io_read() / io_write()
                                    ③ 若可立即完成,直接完成
                                    ④ 若需等待,提交给
                                       block 层后返回

5. 轮询/等待完成              ──►   5. 内核将结果写入 CQ
   while (1) {                          io_cqring_fill_event()
       // 方式 A: 无锁轮询           cqe->user_data = SQE.user_data
       head = *cq_ring->head;          cqe->res = bytes_written
       if (head != cq_ring->tail) {    smp_store_release(tail)
           cqe = &cq_ring->cqes[head & mask];
           process_result(cqe);
           head++;
       }

       // 方式 B: 用 io_uring_enter()
       // 仅在 SQ 为空或需等待时调用 syscall
       io_uring_enter(ring, 0, 1, IORING_ENTER_GETEVENTS);
   }

⚡ 三种 IO 模式及其演进

模式 引入版本 原理 延迟特征
IRQ 驱动 (默认) 5.1 请求提交给 block 层,硬件完成后硬中断触发,软中断完成 CQE 中等,有中断开销
SQPOLL 5.1 内核起一个内核线程(io_sq_wq)持续轮询 SQ,应用不需任何 syscall 提交 低,无 syscall
IOPOLL 5.1 用户线程在 io_uring_enter() 中主动轮询硬件完成队列 最低,但占用 CPU

SQPOLL 的极致场景:应用只需通过共享内存写 SQE,内核线程秒级消费,零系统调用即可完成 IO。这在 Redis、数据库等场景中效果显著。

📊 与传统方案对比

维度 read/pwrite (同步) epoll + 线程池 AIO (libaio) io_uring
syscall 次数/IO 2 ~2-3 2 0 ~ 1 (带 SQPOLL 可为 0)
内存拷贝 有 有 有 共享内存无拷贝
支持 buffered IO ✅ ✅ ❌(仅 O_DIRECT) ✅
支持网络 IO ✅ ✅ ❌ ✅ (5.1+)
支持 fsync/statx/… 各自 syscall 各自 syscall ❌ 所有操作统一接口
每次 IO 动态分配内存 是 是 是 ❌ 预分配,运行时零分配

💡 关键设计哲学总结

  1. 减少 syscall 次数:批量化提交,配合 SQPOLL 完全消除
  2. 减少内存拷贝:mmap 共享环形队列,IO 请求/完成在共享内存传递
  3. 减少动态分配:SQE/CQE 数组预先分配,运行时不需要 kmalloc/kfree
  4. 减少锁竞争:利用 memory ordering (smp_store_release / smp_load_acquire) 实现单生产者单消费者无锁通信
  5. 统一接口:read/write/accept/connect/fsync/statx/openat/splice 等 40+ 种操作统一通过一套接口

🔗 延伸阅读


⚠️ 免责声明

本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。