io_uring读文件不一定比read()快,因其专为高吞吐、低延迟、大批量异步IO设计,在小文件、单次读、无并发场景下通常更慢,且需满足内核版本、O_DIRECT、内存对齐等严格条件。

为什么 io_uring 读文件不一定比 read() 快
直接结论:io_uring 在小文件、单次读、无并发场景下,大概率更慢。它不是“加速器”,而是为高吞吐、低延迟、大批量异步 IO 设计的调度框架。
常见错误现象:io_uring_submit() 返回 -1、errno=ENOSYS(内核不支持),或测出来 latency 更高、CPU 占用反而飙升。
- 必须 Linux 5.1+ 内核,且编译时启用
CONFIG_IO_URING=y(Ubuntu 20.04 默认没开,得自己装新内核) - 小文件(io_uring 反而多出两次内核态切换(提交 + 完成通知),不如直接
read()一次搞定 - 没配
IORING_SETUP_IOPOLL或没用IORING_OP_READ而是绕路走IORING_OP_READV+ 分散 IO,白白增加 sqe 构造开销
怎么写一个真正能跑起来的 io_uring 文件读取最小闭环
核心不是“怎么发请求”,而是“怎么初始化、怎么等结果、怎么防崩溃”。很多示例漏掉 io_uring_queue_init() 失败检查,一运行就段错误。
- 先调
io_uring_queue_init(32, &ring, IORING_SETUP_SQPOLL)—— 注意第二个参数是struct io_uring*指针,不是地址取值;32 是 sq 队列大小,别设太大(>256 会触发内核额外内存分配) - 用
io_uring_prep_read()填充 sqe,目标 fd 必须是O_DIRECT打开的(否则io_uring会退化成同步路径),且 buffer 地址需 512 字节对齐(posix_memalign()分配) - 提交后不能直接
sleep()等,要用io_uring_wait_cqe()+io_uring_cqe_seen()成对处理完成事件,漏掉cqe_seen会导致后续 submit 失败
int fd = open("data.bin", O_RDONLY | O_DIRECT);
void *buf;
posix_memalign(&buf, 512, 4096);
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, 4096, 0);
io_uring_sqe_set_data(sqe, buf); // 方便后续识别
io_uring_submit(&ring);
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
if (cqe->res < 0) { /* 处理错误 */ }
io_uring_cqe_seen(&ring, cqe);
IORING_SETUP_SQPOLL 开还是不开?看你的 workload
这个 flag 不是“开了就快”,它让内核起一个专用线程轮询 submission queue,省去系统调用开销,但代价是:多占一个 CPU 核、内存锁竞争变高、小负载下反而拖慢整体响应。
立即学习“C++免费学习笔记(深入)”;
- 适合:持续高频提交(>10k ops/sec)、IO 密集型服务(如 proxy、DB 存储引擎)
- 不适合:命令行工具、单次读多个小文件、开发机测试(SMP 调度抖动大,
SQPOLL线程可能被频繁抢占) - 验证是否生效:
ps aux | grep io_uring应该看到类似io_uring-sq-1234的线程;若没看到,说明内核没启用或 init 时没传 flag
为什么读完数据却拿不到内容?对齐和缓存是隐形杀手
最常被忽略的两个点:buffer 地址没对齐 + 忘了 __builtin_ia32_clflush_opt() 或 msync() 刷 cache —— 尤其在用 O_DIRECT 时,用户空间 buffer 若在 CPU cache 里没同步,内核可能读到脏数据或直接报 -EFAULT。
-
O_DIRECT要求 buffer 地址、offset、length 全部对齐到逻辑块大小(通常是 512B),用posix_memalign(),别用malloc() - 读完后如果立刻用
memcpy()或打印内容,确保 buffer 已从 cache 刷出(O_DIRECT绕过 page cache,但 CPU cache 还在);简单起见,加一句__builtin_ia32_clflush_opt(buf)(x86)或改用memfd_create()+mmap()配合MAP_SYNC(ARM64) - 别在
io_uring回调里做复杂计算——它运行在内核上下文(尤其是SQPOLL模式),禁止调用malloc、printf、锁等用户态函数
实际跑通的关键不在“怎么发 IO”,而在确认三件事:内核真支持、buffer 真对齐、cqe 真消费。少一个,程序就静默失败或数据错乱。



















