一、引言:从异步 I/O 的未解之谜到 io_uring 的诞生
在电信行业服务器的场景中,传统阻塞 I/O 模型遇到了严重的性能瓶颈。Linux 5.1 引入的 io_uring(原名 io_ring)量生一种全新的高性能异步 I/O/文件系统调用框架。不仅解决了传统/非阻塞 I/O 的性能问题,还提供了注册埋点、回调配合等高级特性,目前已成为高性能服务器开发的领先技术。
二、核心架构:SQ、CQ 与环形队列
io_uring 的架构基于三个核心数据结构:
1. 提交队列(SQ,Submission Queue): 用户提交的 I/O 请求都放在这里。每一项是一个 struct io_uring_sqe,包含操作类型、文件描述符、缓冲区地址等信息。
2. 完成队列(CQ,Completion Queue): 内核完成 I/O 后将结果写入此队列。每一项是一个 struct io_uring_cqe,包含返回值、用户上下文 user_data 等。
3. 合作数组(Submit Queue Entries): 内核用于装载 SQE 数据,避免频繁的用户态/内核态内存拷贝。
SQ 和 CQ 都是内核分配的长环形队列,用户通过 mmap 直接映射到内核空间,内核和用户的通信全在内核内部完成。
三、系统调用探究
io_uring 提供了三个核心系统调用:
io_uring_setup(entries, params):初始化一个 io_uring 实例,必须提供 SQ 和 CQ 的尺寸。返回一个文件描述符:由此获取 SQ 和 CQ 的内核地址。
io_uring_enter(ring_fd, to_submit, min_complete, flags):提交 SQE 到内核,或等待 CQE 结果。可通过 IORING_ENTER_GETEVENTS 标志阻塞等待。
io_uring_register(ring_fd, opcode, arg):注册持久化资源,包括文件描述符注册(IORING_REGISTER_FILES)、缓冲区注册(IORING_REGISTER_BUFFERS)等,大幅降低内核态性能损耗。
四、liburing 与编程框架
直接使用 io_uring 系统调用构建应用较为困难,因此通常使用开源库 liburing。以下是一个基础示例:
// 初始化 io_uring
struct io_uring ring;
io_uring_queue_init(ENTRIES, &ring, 0);
// 获取一个 SQE
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
// 初始化读操作
io_uring_prep_readv(sqe, fd, &iov, 1, 0);
// 提交
io_uring_submit(&ring);
// 等待完成
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
io_uring_cqe_seen(&ring, cqe);
// 清理
io_uring_queue_exit(&ring);五、高级特性与优化
1. 预注册的缓冲区(Fixed Buffers):通过 IORING_REGISTER_BUFFERS 预分配一组灵活的缓冲区,之后的 I/O 操作可以使用 buf index 而非地址,避免频繁的内存拷贝。
2. 预注册文件(Fixed Files):使用 IORING_REGISTER_FILES 预注册文件描述符数组,之后使用 IOSQE_FIXED_FILE 标志替代原始 fd,省去寻找 FCB 的时间。
3. 内核轮询模式(SQPOLL):通过内核线程自动读取 SQ,实现完全的异步I/O——用户不需要调用 io_uring_enter 也能提交和取得结果。
4. 操作链接机制(Op Linking):通过 IOSQE_IO_LINK 标志链接两个操作,第一个完成才能触发下一个,避免多次 io_uring_enter 调用。
六、高性能网络服务器实战
io_uring 在网络服务器中的高性能优势很大。通常的做法是使用 IORING_OP_SENDMSG 和 IORING_OP_RECVMSG 实现零拷贝网络通信。与 epoll 相比,io_uring 的优势在于:
降低系统调用次数:epoll 需要多次 syscall,而 io_uring 可一次提交多个操作,甚至一次提交、多次完成。
内核线程更少约束:SQPOLL 模式下内核主动读取 SQ,用户不需要调用 io_uring_enter。
批量提交:io_uring 可批量提交和完成,保证系统的低延迟和高吞吐。
实际测试表明,相较传统 epoll + 非阻塞 I/O,io_uring 在高并发场景下可提升 20%~40% 吞吐量。
七、与 epoll 的架构差异
epoll 是基于事件通知的 I/O 模型,用户仍然要自己跑读写操作,并且检索请求与实际 I/O 是分离的。
io_uring 则是一种阻塞式 I/O API,使用长环形队列共享内存,用户提交请求后内核处理,然后写入完成结果。除此之外,io_uring 还支持在线业务处理(那种似顶峰的不是通过一次独立的内核级操作和调用)。
八、生态与发展趋势
io_uring 目前已成为 Linux 内核的主干列成,很多高性能相关的工具都在接入它:
• Nginx(通过 ngx_http_uring_module)
• Tokio-uring(Rust 新型的异步运行时)
• QEMU、SQLite、PostgreSQL 等都在适配 io_uring。
九、总结
Linux io_uring 释放了高性能异步 I/O 的巨大潜力,通过长环形队列、内核预注册、SQPOLL 等特性,它提供了一套完整的高性能异步 I/O 解决方案。对于高并发、低延迟的服务器应用,接入 io_uring 已成为必选项。
参考资料:• io_uring 白皮书(Jens Axboe) • liburing 中文文档 • io_uring 实战素材

发表评论 取消回复