io_uring 是什么?一个环形队列批量搞定异步 IO
答案:io_uring 是 Linux 内核的异步 IO 框架,用一对共享内存环形队列(Submission Queue + Completion Queue)在用户态与内核态之间提交/收割 IO,一次 io_uring_enter() 批量处理多笔操作。核心收益 = 批量提交 + 免系统调用。
一、为什么会有 io_uring
传统 read/write 每笔操作都是一次系统调用;Linux AIO(aio_read)只支持 O_DIRECT、接口别扭、完成通知靠信号。io_uring 用共享内存环形队列解决:用户把操作写进 SQ,内核完成后写进 CQ,两边都能批量搬运,把"每笔一次 syscall"变成"一批一次 syscall"。
二、最快理解(三个环)
用户态写操作 → [SQ 提交队列] → 内核执行
内核写结果 → [CQ 完成队列] → 用户态收割- SQ(Submission Queue):用户往队列尾填
io_uring_sqe,一次可填多个; - CQ(Completion Queue):内核完成一笔写一个
io_uring_cqe到队列尾,用户批量收割。
三、三种模式(按延迟/资源取舍)
| 模式 | 提交方式 | 系统调用 | 适用 |
|---|---|---|---|
| 默认 | 每次 io_uring_enter() 提交+收割一批 | 每批 1 次 | 通用 |
| SQPOLL | 内核线程轮询 SQ | 0(用户态免 syscall) | 延迟敏感,占 1 个 CPU 核 |
| IOPOLL | 设备端轮询(NVMe 等) | 0 | 极致低延迟 |
四、性价比判断(什么时候值得用)
- 值得:高 IOPS(数据库/存储引擎/代理)、低延迟网络服务;
- 可缓:普通低吞吐应用(几十 K IOPS 内),传统 read + 线程池 + O_DIRECT 通常够用。
五、常见坑
- 内核版本:5.1+ 可用、6.0+ 才完善;
- 非特权用户访问受限:
kernel.io_uring_disabled内核参数控制; - SQPOLL 模式会固定占一个 CPU 核做轮询,别在核紧张时盲目开;
- 与 epoll 集成别重复等待:用
IORING_OP_POLL_ADD并入队列,而不是两个框架各等各的。
深度入口
- 接口结构、性能量化对比(IOPS/延迟)、C 示例:io_uring 异步 IO 框架深入
- 与页缓存、Direct IO 的关系:Direct IO vs 页缓存
- 零拷贝视角对比 sendfile/splice:零拷贝深入
FAQ
Q: io_uring 是什么? A: Linux 内核异步 IO 框架(5.1+),用共享内存环形队列(SQ/CQ)提交和收割 IO,一次 io_uring_enter() 批量处理多笔,省掉每笔一次系统调用。
Q: io_uring 比传统 read/write 快在哪? A: 批量 + 免系统调用。传统每笔一次 syscall;io_uring 默认一次 enter 提交/收割一批,SQPOLL 模式下用户态连 syscall 都省了。
Q: io_uring 怎么用?给个最小示例 A: io_uring_queue_init → io_uring_get_sqe → io_uring_prep_read/write → io_uring_submit → io_uring_wait_cqe → io_uring_cqe_seen。
Q: io_uring 的三种模式怎么选? A: 默认(一次 enter 批量,通用);SQPOLL(内核轮询、用户态零 syscall、占一个核);IOPOLL(设备轮询、延迟最低、仅 NVMe 等)。
Q: io_uring 和 epoll 是什么关系? A: 互补而非替代。epoll 只做事件通知,io_uring 用 IORING_OP_POLL_ADD 也能等事件,还把 accept/recv/send 并入队列,做高并发网络服务更彻底。
Q: 什么场景才值得上 io_uring? A: 高 IOPS(数据库/存储引擎/代理)与低延迟网络服务收益最大;低吞吐应用传统 read 加线程池通常够用,不必引入复杂度。
一句话总结:io_uring 用一对共享环形队列把"每笔一次系统调用"变成"一批一次",高 IOPS/低延迟场景收益最大,普通应用先评估再上。