一、引言:从异步 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 新型的异步运行时)

• Netty io_uring 传输层

• QEMU、SQLite、PostgreSQL 等都在适配 io_uring。

九、总结

Linux io_uring 释放了高性能异步 I/O 的巨大潜力,通过长环形队列、内核预注册、SQPOLL 等特性,它提供了一套完整的高性能异步 I/O 解决方案。对于高并发、低延迟的服务器应用,接入 io_uring 已成为必选项。

参考资料:• io_uring 白皮书(Jens Axboe) • liburing 中文文档 • io_uring 实战素材

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部