本页目录
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
基本用法
struct io_uring ring;
; // 256 entries in SQ
// 准备一个 read 请求:
struct io_uring_sqe *sqe = ;
; // offset: fixed position
sqe->user_data = 42; // 用户标签 (返回在 CQE 中)
// 提交请求:
;
// 收割完成:
struct io_uring_cqe *cqe;
; // 阻塞等 1 个完成
;
;
高级特性
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:
; // 固定 buffer
; // 固定文件
// 之后用 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_uring | libaio | POSIX aio | |
|---|---|---|---|
| 完成通知 | 轮询 CQ | io_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