Linux io_uring SQE 怎样组织异步操作
我第一次把多个文件读取请求放进 io_uring 时,最容易混淆的不是 API 名字,而是“哪个对象描述请求、哪个对象表示完成”。可以先记住一句话:SQE 是提交给内核的操作描述,CQE 是操作结束后的结果回执。应用先取得 SQE,填写操作参数,批量提交;完成后再从 CQE 读取结果,并用 user_data 找回自己的请求上下文。
这篇只围绕提交队列、完成队列和请求生命周期展开,不把 SQPOLL、固定缓冲区和网络协议优化混在一起。这样组织后,异步操作的代码会更像“准备任务清单,再集中收结果”,而不是为每次 I/O 手写一套状态机。
先分清 SQ、SQE 与 CQE
SQ 是提交队列,应用把待处理的操作放进去;SQE 是其中一条操作记录,描述“对哪个 fd 做什么、读写哪块缓冲区”;CQ 是完成队列,内核把完成事件放进去;CQE 则携带这次操作的结果。一个 SQE 通常对应一个 CQE,但需要跳过成功完成事件的特殊配置除外,基础代码先按一一对应理解即可。

SQE 中常用的信息包括操作码、文件描述符、偏移、缓冲区地址、长度和 user_data。不要把 user_data 当成内核要解析的业务字段,它更适合作为一个不透明的关联值:可以放请求编号,也可以放指向业务结构体的指针。
用 SQE 组织一次异步读取
使用 liburing 时,通常先调用 io_uring_get_sqe 取得一条可用 SQE,再用 io_uring_prep_read 填充读取参数,最后通过 io_uring_sqe_set_data 绑定自己的上下文。下面是一个最小的组织方式,重点在对象之间的关系;其中的文件打开、队列初始化和资源释放代码按实际程序补齐。
#include#include #include struct read_request { int fd; // 业务请求持有的文件描述符 char *buffer; // 提交给异步读取的目标缓冲区 unsigned length; // 本次读取允许写入的字节数 unsigned long id; // 用于在 CQE 中识别请求 }; static int prepare_read(struct io_uring *ring, struct read_request *req) { struct io_uring_sqe *sqe = io_uring_get_sqe(ring); if (sqe == NULL) { // SQ 没有空位时,先提交已有 SQE,再重试获取 return -EAGAIN; } // prep helper 负责把读取操作的 fd、缓冲区、长度和偏移写入 SQE io_uring_prep_read(sqe, req->fd, req->buffer, req->length, 0); // user_data 会原样带到对应 CQE,便于找回业务请求 io_uring_sqe_set_data(sqe, req); return 0; } static int submit_requests(struct io_uring *ring) { int submitted = io_uring_submit(ring); if (submitted
这里有一个很重要的边界:io_uring_get_sqe 返回空指针时,不代表读取失败,而是当前提交队列暂时没有可用 SQE。应用可以把已有条目提交出去,等内核消费后再继续获取。准备好多条 SQE 后,调用一次 io_uring_submit,就能把这一批请求交给内核。
批量提交后,怎样从 CQE 找回请求
提交动作只表示请求进入了处理路径,不表示数据已经读完。完成处理通常由 io_uring_wait_cqe 或 io_uring_peek_cqe 取得 CQE,再读取 cqe->res 和绑定的数据。对读取操作来说,res 是实际读取的字节数;失败时是负的 errno 值,例如 -EIO,而不是传统同步调用那种“返回 -1,再看 errno”。

static int reap_one(struct io_uring *ring) {
struct io_uring_cqe *cqe = NULL;
int ret = io_uring_wait_cqe(ring, &cqe);
if (ret res id, cqe->res);
} else {
// 正数表示本次读取实际完成的字节数
printf("request %lu read %d bytes\\n", req->id, cqe->res);
}
// 每个已取出的 CQE 都要标记为已消费,避免 CQ 堵住
io_uring_cqe_seen(ring, cqe);
return 0;
}
io_uring_cqe_seen 的位置也值得注意:应该在完成事件已经被业务读取后调用。如果提前释放请求结构体、缓冲区或文件描述符,CQE 里保存的 user_data 就可能指向已经失效的内存。批量场景下,可以让每个请求结构体拥有自己的缓冲区,并以“已看到 CQE”作为回收信号。
队列满和指针生命周期怎么处理
组织 SQE 时,我会把问题拆成两层。第一层是队列容量:获取不到 SQE 就提交现有批次,或者降低单次批量大小;不要无限制地在用户态创建“待提交”对象。第二层是指针稳定性:SQE 可能只保存缓冲区地址,缓冲区在内核仍需要它时不能被释放。未启用 SQPOLL 的常规场景下,提交调用完成后,很多只在提交阶段需要的数据可以释放;启用 SQPOLL 或遇到较老内核特性时,应按对应能力保证指针直到完成事件都有效。
| 现象 | 优先检查 | 处理思路 |
|---|---|---|
| get_sqe 返回 NULL | SQ 是否已满 | 先 submit 已准备条目,再获取下一条 |
| submit 返回负数 | 提交阶段的负 errno | 保留同一批请求,按错误决定重试或终止 |
| CQE res 为负数 | 具体 I/O 的错误码 | 按请求上下文处理,不把它当作全局 errno |
| 完成后数据异常 | buffer 与 user_data 生命周期 | 在 cqe_seen 后再回收关联资源 |
最后的组织原则
如果只记三点:一是 SQE 只负责描述操作,二是 io_uring_submit 负责把一批描述交给内核,三是 CQE 的 res 和 user_data 负责把结果交还业务层。这样写出来的代码,队列容量、缓冲区生命周期和错误处理都有明确落点,也方便之后再引入链接请求、固定资源或 SQPOLL,而不会把基础模型改乱。
相关问题
io_uring 的 SQE 能不能重复使用?
可以在它已被内核消费、并且关联的缓冲区和上下文已经安全回收后重新取得新的 SQE。不要把仍在处理中的 SQE 或其指针当作可复用对象。
为什么不直接把业务对象地址塞进 user_data?
可以这样做,但必须保证对象在对应 CQE 被取出前一直有效。若对象会移动、复用或提前释放,改用稳定的请求编号和独立映射表更稳妥。
journalctl -b 怎样筛选本次启动日志
- 上一篇
- journalctl -b 怎样筛选本次启动日志
- 下一篇
- Linux getrandom 设置 GRND_NONBLOCK 后为什么返回 EAGAIN
-
- 文章 · linux | 28分钟前 | 命令行工具 · linux运维 · 故障排查 · Linux systemd journalctl 日志排查 启动日志 Boot ID systemd-journald
- journalctl -b 怎样筛选本次启动日志
- 311浏览 收藏
-
- 文章 · linux | 30分钟前 | linux运维 · 文件系统 · Linux 文件句柄 name_to_handle_at open_by_handle_at file_handle mount_id
- Linux name_to_handle_at 怎样保存可复用的文件句柄
- 340浏览 收藏
-
- 文章 · linux | 33分钟前 |
- systemd.timer OnBootSec 与 OnUnitActiveSec 的周期触发配置方法
- 351浏览 收藏
-
- 文章 · linux | 33分钟前 |
- systemd socket 单元 Backlog 怎样影响连接排队
- 490浏览 收藏
-
- 文章 · linux | 37分钟前 | linux运维 · 故障排查 · 服务治理 · Linux systemd 故障恢复 服务重启 RestartSec StartLimit Restart RestartSteps
- systemd RestartSec 怎样影响服务重启节奏
- 312浏览 收藏
-
- 文章 · linux | 39分钟前 |
- Linux openat2 resolve flags 怎样限制路径解析范围
- 132浏览 收藏
-
- 文章 · linux | 41分钟前 | linux运维 · Linux cgroup v2 frozen cgroup.freeze cgroup.events populated 进程层级 运维监控
- Linux cgroup v2 cgroup.events 怎样监控层级冻结状态
- 332浏览 收藏
-
- 文章 · linux | 44分钟前 |
- Linux /proc/PID/statm 怎样区分虚拟内存与常驻内存
- 481浏览 收藏
-
- 文章 · linux | 48分钟前 |
- Linux /proc/PID/fd 怎样判断进程打开的文件
- 313浏览 收藏
-
- 文章 · linux | 50分钟前 | Linux statx STATX_BTIME 文件创建时间
- Linux statx 怎样读取文件创建时间
- 165浏览 收藏
-
- 文章 · linux | 52分钟前 | linux运维 · Linux 线程 i/o 进程 load average total /proc/loadavg running
- Linux /proc/loadavg 的 running 与 total 怎么读
- 329浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 1029次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1030次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1173次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1134次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 974次使用
-
- 封装 C 库句柄并明确创建、释放与线程约束
- 2026-10-08 400浏览
-
- Go netlink 怎样向内核发送只读查询消息
- 2026-10-31 356浏览
-
- Go malgo 怎样配置立体声PCM播放回调
- 2026-10-31 307浏览
-
- Go 指针为什么不能随意交给 C 长期保存,规则保护了什么
- 2026-10-08 236浏览
-
- Go cgo 读取 C 结构体字段时怎么处理布局和类型转换
- 2026-11-11 468浏览

