关于 io-uring
面向 Agent 的 Linux io_uring 指南,涵盖队列模型、liburing API、异步操作和 Rust 集成。适用于评估高吞吐网络或磁盘服务的 I/O 实现,并说明性能取舍与安全注意事项。
主要功能
- 01SQE 与 CQE 队列模型
- 02liburing 常用读写与网络操作
- 03多次 accept/recv 与缓冲区环
- 04固定文件、注册缓冲区和零拷贝发送
- 05tokio-uring Rust 集成
适用场景
- 01构建高吞吐 Linux 网络或磁盘服务
- 02评估 io_uring 与 epoll 的差异
- 03为高连接数服务实现多次 accept/recv
- 04在 Rust 中集成异步 I/O
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill io-uring安装后在 Claude Code 中输入/io-uring确认已加载。
详细介绍
io_uring
用途
引导 Agent 使用 Linux io_uring:提交/完成队列模型(SQE/CQE)、liburing API、多次触发的 accept/recv、预置缓冲区环、固定文件和注册缓冲区、零拷贝发送、通过 tokio-uring 集成 Rust、与 epoll 的性能比较,以及安全注意事项。
使用场景
- 在 Linux 5.1+ 上构建高吞吐量网络或磁盘服务
- 用更少的系统调用取代 epoll + 线程池
- 为连接密集型服务实现多次触发的 accept/recv
- 使用
IORING_OP_SEND_ZC进行零拷贝网络传输 - 通过
tokio-uring在 Rust 中集成异步 I/O - 针对你的工作负载评估 io_uring 与 epoll
工作流
1. SQ/CQ 模型
Application Kernel
│ │
├── mmap SQ ring ───────────►│ submission queue (SQE)
├── mmap CQ ring ◄──────────│ completion queue (CQE)
├── io_uring_submit() ──────►│ processes SQEs
└── io_uring_wait_cqe() ◄──│ posts CQEs
每个 SQE 描述一个操作;每个 CQE 报告结果和 user_data 标识值。
2. 最简 liburing 示例
#include <liburing.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
struct io_uring ring;
io_uring_queue_init(32, &ring, 0);
int fd = open("test.txt", O_RDONLY);
char buf[4096];
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, sizeof(buf), 0);
io_uring_sqe_set_data(sqe, (void *)1);
io_uring_submit(&ring);
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
if (cqe->res >= 0)
printf("read %d bytes\n", cqe->res);
else
perror("read");
io_uring_cqe_seen(&ring, cqe);
io_uring_queue_exit(&ring);
close(fd);
return 0;
}
gcc -o uring_read uring_read.c -luring
./uring_read
3. 常见的 prep 操作
| 函数 | 操作 |
|---|---|
io_uring_prep_read | 文件读取 |
io_uring_prep_write | 文件写入 |
io_uring_prep_recv | Socket 接收 |
io_uring_prep_send | Socket 发送 |
io_uring_prep_accept | 接受连接 |
io_uring_prep_connect | 发起出站连接 |
io_uring_prep_poll_add | 轮询 fd |
io_uring_prep_timeout | 超时/链接超时 |
// Batch multiple SQEs before single submit
for (int i = 0; i < n; i++) {
sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fds[i], bufs[i], sizes[i], 0);
io_uring_sqe_set_data(sqe, (void *)(intptr_t)i);
}
io_uring_submit(&ring);
4. 多次触发操作
// Multi-shot accept — one SQE handles many connections
sqe = io_uring_get_sqe(&ring);
io_uring_prep_multishot_accept(sqe, listen_fd, NULL, NULL, 0);
io_uring_sqe_set_data(sqe, (void *)ACCEPT_COOKIE);
// Loop on CQEs until CQE_FLAG_MORE is clear
while (1) {
io_uring_wait_cqe(&ring, &cqe);
if (cqe->user_data == ACCEPT_COOKIE) {
int client_fd = cqe->res;
if (client_fd >= 0)
handle_client(client_fd);
if (!(cqe->flags & IORING_CQE_F_MORE))
break; // re-arm accept SQE
}
io_uring_cqe_seen(&ring, cqe);
}
5. 预置缓冲区环
#include <liburing.h>
#define BUF_GROUP 0
#define BUF_SIZE 4096
#define BUF_COUNT 64
struct io_uring_buf_ring *buf_ring;
char bufs[BUF_COUNT][BUF_SIZE];
// Setup buffer ring
buf_ring = io_uring_setup_buf_ring(&ring, BUF_COUNT, BUF_GROUP, 0, &ret);
for (int i = 0; i < BUF_COUNT; i++)
io_uring_buf_ring_add(buf_ring, bufs[i], BUF_SIZE, i, BUF_COUNT - 1 - i, 0);
io_uring_buf_ring_advance(buf_ring, BUF_COUNT);
// Recv with provided buffers
sqe = io_uring_get_sqe(&ring);
io_uring_prep_recv_multishot(sqe, sock_fd, NULL, 0, 0);
sqe->buf_group = BUF_GROUP;
内核从缓冲区环中选择缓冲区,免去每次 recv 时分配缓冲区。
6. 固定文件和注册缓冲区
// Register files — avoids per-op fd table lookup
int fds[64];
io_uring_register_files(&ring, fds, 64);
io_uring_prep_read(sqe, 0, buf, size, offset); // index 0, not fd
// Registered buffers — pinned, DMA-friendly
struct iovec iov = { .iov_base = buf, .iov_len = size };
io_uring_register_buffers(&ring, &iov, 1);
io_uring_prep_read_fixed(sqe, fd, buf, size, offset, 0);
7. 零拷贝发送
sqe = io_uring_get_sqe(&ring);
io_uring_prep_send_zc(sqe, sock_fd, data, len, 0, 0);
// May complete with IORING_CQE_F_MORE — wait for notification CQE
需要内核 6.0+,以及 NIC/驱动支持。若不可用,则回退到复制模式。
8. tokio-uring(Rust)
use tokio_uring::fs::File;
#[tokio_uring::main]
async fn main() -> std::io::Result<()> {
let file = File::open("test.txt").await?;
let buf = vec![0u8; 4096];
let (res, buf) = file.read_at(buf, 0).await;
println!("read {} bytes", res?);
Ok(())
}
cargo add tokio-uring
9. io_uring 与 epoll
| 方面 | epoll | io_uring |
|---|---|---|
| 系统调用 | 每次操作都要调用 epoll_wait + read/write | 批量提交 SQE,只需一次提交 |
| 缓冲区管理 | 应用分配 | 预置缓冲区 |
| 零拷贝 | sendfile、手动实现 | 内置 SEND_ZC |
| 复杂度 | 较低 | 较高 |
| 内核版本 | 2.6+ | 5.1+(功能因版本而异) |
Choose io_uring when
├── Syscall overhead is measurable bottleneck
├── High connection count with multi-shot
└── Kernel 6.x+ with needed op support
Stay on epoll when
├── Simple app, portability matters
└── Team familiarity outweighs gains
10. 安全注意事项
# Restrict unprivileged io_uring (some distros disable by default)
sysctl kernel.io_uring_disabled # 0=enabled, 1=disabled, 2=allow registered
sysctl kernel.io_uring_group # gid allowed when disabled=2
io_uring 曾被用作攻击面——请保持内核更新;对不受信任的代码进行沙箱隔离。
常见问题
| 现象 | 原因 | 解决方法 |
|---|---|---|
prep 时出现 -EINVAL | 内核不支持该操作 | 检查 io_uring_get_probe() |
| wait_cqe 时挂起 | 未提交 | 添加 SQE 后调用 io_uring_submit |
| 缓冲区未归还 | 预置缓冲区未重新添加 | 消耗后调用 buf_ring_add |
| SEND_ZC 未完成 | 需要第二个 CQE | 等待 IORING_CQE_F_MORE 清除 |
| 比 epoll 慢 | 批次太小 | 批量提交更多 SQE;使用 SQPOLL |
| 权限被拒绝 | io_uring 已禁用 | 使用 sysctl 设置或通过 capability 运行 |
相关技能
skills/async-io/dpdk— 网络的内核旁路替代方案skills/async-io/af-xdp— 用于数据包 I/O 的 XDP Socket 路径skills/observability/ebpf— 跟踪 io_uring 事件skills/rust/rust-async-internals— tokio-uring 背后的异步模型skills/profilers/strace-ltrace— 与 epoll 进行系统调用对比skills/kernel/kernel-internals— 内核 I/O 子系统背景知识
评论
支持 Markdown