利用 io_uring 实现高性能异步 Linux 应用

发布: (2026年2月9日 GMT+8 07:52)
7 分钟阅读
原文: Dev.to

Source: Dev.to

在共享内存中,针对 io_uring Linux I/O 系统调用,序列队列条目和完成队列条目的环形缓冲区布局方式

作者: Sospeter Kinyanjui

简介

很长一段时间里,Linux 只提供了 epoll——一种 I/O 通知机制,允许应用程序向内核发起 read/write 系统调用。
epoll 首次出现在 Linux 2.5.44(2002 年),并在 2.6(2003 年)成为主流。它通过三个系统调用 epoll_createepoll_ctlepoll_wait 使用 就绪模型。内核在资源就绪时通知应用程序,应用程序随后提交工作。

由于内核仅在有东西就绪时才通知,这种模型的复杂度是 O(1)——无论监视 10 条连接还是 10 000 条,成本相同。然而,每一次通知仍然需要一次系统调用,这就产生了昂贵的 系统调用税:每个事件都要从用户态切换到内核态。

直到 2019 年,io_uring 才出现,提供了一个真正的异步 I/O 接口,所需的系统调用大幅减少。

什么是异步执行?

应用程序能够启动一个长时间运行的任务并继续执行其他工作,而无需等待该任务完成的能力。

异步执行可以更好地利用 CPU 和 I/O 资源。虽然 epoll 是事件驱动的(因此只是一种“异步”幻象),但 io_uring 实际上会批量提交多个 I/O 请求,只需一次系统调用,就能让读写操作独立进行。

定义与实现

io_uring 提供了三个系统调用:

调用目的
io_uring_setup(2)创建 提交队列(SQ)和 完成队列(CQ),并返回文件描述符。它会配置环形缓冲区(headtailring_maskring_entries)。
io_uring_enter(2)告诉内核 “我已经在环中放置了 SQE,请去处理它们”。
io_uring_register(2)预先向内核注册资源(例如缓冲区、文件),以避免每次请求的查找。

io_uring_setup

  • 分配一个共享内存区域,用于保存 SQ 和 CQ 结构。
  • 用户空间对 SQ 具有 权限(内核读取)。
  • 内核对 CQ 具有 权限(用户读取)。
  • 该设计遵循 单生产者 / 单消费者 模型,以获得最高性能。

io_uring_enter

整个操作的 “引擎启动器”。其原型:

#include <sys/syscall.h>

int io_uring_enter(unsigned int fd,
                   unsigned int to_submit,
                   unsigned int min_complete,
                   unsigned int flags,
                   sigset_t *sig);

调用 io_uring_enter 会通知内核有 to_submit 个 SQE 已准备好进行处理。

io_uring_register

为你的数据提供 “VIP 通行证”。通过预先注册缓冲区或文件,内核可以直接使用它们,无需额外的查找或映射,从而消除大量开销。

生态系统和语言绑定

C 开发者可以使用官方的 liburing 库,它封装了三个系统调用并提供辅助函数。

Rust 也对 io_uring 提供了强大的支持,提供内存安全保证,防止内核和应用同时访问同一缓冲区的经典“危险区”。Rust 编译器确保在内核通过 CQE 返回之前,用户代码不能触碰该缓冲区。

流行的 Rust crate 包括:

  • tokio-uring – 将 io_uring 与 Tokio 异步运行时集成。
  • glommio – 基于 io_uring 构建的每核一线程框架。
  • 其他:io-uringuring-sys 等。

还有更多

基于完成的 I/O 并非 Linux 独有:

操作系统机制特点
WindowsI/O 完成端口 (IOCP)异步,但仍需对每个请求进行一次系统调用,导致的系统调用开销高于 io_uring
macOSkqueue基于就绪;必须调用 kevent 来发现就绪状态,然后再为实际 I/O 发起单独的系统调用,产生了 io_uring 本想消除的同样系统调用开销。

因此,io_uring 代表了 Linux 上真正的异步编程模型,最大限度地减少了高性能 I/O 所需的系统调用次数和上下文切换。

“做最好的齿轮,但要记住你并不是唯一的齿轮。” ——提醒我们并非所有问题都必须自己解决;有时合适的工具(如 io_uring)就足以产生巨大影响。

真正的问题:跨平台、基于完成的异步运行时

从我的个人视角来看,我认为真正的问题在于创建一个 跨平台基于完成 的异步运行时。这类技术已经存在:我们有 compio,一个用于异步 I/O 操作的 Rust 框架。

缺失的是什么?

  1. 零成本抽象 – 可以说 compio 并未提供真正的零成本抽象。
  2. 固定缓冲区 – 它使用固定缓冲区(io_uring 的设计选择),这些缓冲区是不可变引用。

大多数 Rust 生态系统都是基于 std::io::Readstd::io::Write trait 构建的,这些 trait 期望 可变 引用。而 compio 则强调 缓冲区的所有权 而不是借用。这与 io_uring 的基于完成模型非常契合,但也导致了它与生态系统其他部分的真实集成问题。

“但正如我所说,我们只能在这里,一次实现一个方案。即使看似不可能,也要相信自己。下次再见,和平、专注、渴望。”

保持联系

您可以查看我博客上的其他文章。

  • GitHub:
  • LinkedIn:
0 浏览
Back to Blog

相关文章

阅读更多 »