Linux io_uring 固定缓冲区适合什么 IO 场景
io_uring 固定缓冲区最适合少量、长期存在、会被高频重复使用的 I/O 缓冲池。它在注册时把用户内存映射到内核,并让后续 READ_FIXED/WRITE_FIXED 请求通过 buf_index 复用这份映射,从而避免每次 I/O 都做映射、解除映射和页引用计数操作。它常与 O_DIRECT 文件读写配合,但并不适合一次性 I/O、缓冲区尺寸频繁变化或内存紧张的进程。
liburing 官方项目:https://github.com/axboe/liburing
- 高 IOPS、固定大小块、缓冲池长期复用:优先评估固定缓冲区。
- 一次性命令行工具、低频文件读取、每次都分配不同内存:普通缓冲区通常更简单。
- 网络接收时提交请求前不知道该用哪块空闲内存:考虑 provided buffer ring,而不是把 fixed buffer 硬套进去。
- 是否更快必须用真实队列深度、块大小和设备测量,注册本身有一次性成本且会锁定内存。
哪些任务值得使用固定缓冲区
固定缓冲区的收益来自“注册一次、重复使用”。因此首先看任务,而不是只看 API 是否可用。
| IO 场景 | 适合程度 | 原因 |
|---|---|---|
| 存储服务反复读写固定大小数据块 | 适合 | 缓冲区池稳定,注册成本可被大量 I/O 摊薄 |
| 数据库页、缓存页或日志块的长期缓冲池 | 适合 | 页面会重复使用,索引与生命周期容易管理 |
O_DIRECT 高并发文件 I/O | 常见搭配 | 官方 liburing 文档明确指出可避免每次 I/O 的映射/解除映射和页引用操作 |
| 只读取一次的小文件 | 通常不适合 | 注册、内存管理和清理复杂度可能高于收益 |
| 请求尺寸与内存地址高度动态 | 谨慎 | 固定槽位难复用,更新注册表会增加管理成本 |
| 内存预算很紧的进程 | 谨慎 | 注册内存会被锁定,并计入相应的内存锁定限制 |
固定缓冲区到底固定了什么
io_uring_register_buffers() 接收一个 iovec 数组,把每个元素对应的用户内存登记到当前 ring。注册完成后,iovec 数组本身可以释放;但 iov_base 指向的真实内存仍必须保持有效,直到不再有请求使用并完成注销。
提交固定读写请求时,应用同时传入内存地址、长度和 buf_index。地址与长度必须落在该索引对应的已注册区域内,但地址不要求等于区域起点,因此可以读取注册区域中的一个子范围。

最小实现:注册一块内存并提交 READ_FIXED
下面示例注册一个 4 KiB 缓冲区,从普通文件偏移 0 读取数据。为了让代码重点落在 fixed buffer 关系上,示例没有启用 O_DIRECT;若生产环境启用它,还必须遵守文件系统和设备的地址、长度与偏移对齐要求。
#include#include #include #include #include #include #include #include #define QUEUE_DEPTH 32 #define BLOCK_SIZE 4096 int main(void) { struct io_uring ring; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; struct iovec iov; void *buffer = NULL; int fd = -1; int ret; ret = io_uring_queue_init(QUEUE_DEPTH, &ring, 0); if (ret res res)); else printf("读取完成:%d 字节\n", cqe->res); io_uring_cqe_seen(&ring, cqe); cleanup: close(fd); /* 先确保请求已经完成并注销,再释放底层用户内存。 */ io_uring_unregister_buffers(&ring); free(buffer); io_uring_queue_exit(&ring); return ret
# 使用 pkg-config 取得 liburing 的头文件和链接参数。 cc -O2 -Wall fixed-read.c -o fixed-read $(pkg-config --cflags --libs liburing) # 运行示例;请先准备当前目录下的 data.bin。 ./fixed-read
内存生命周期与错误处理最容易出错
固定缓冲区不是把内存复制给内核,而是建立长期映射。生产代码要把下面几条当成资源契约:
- 注册成功后可以销毁
iovec描述数组,但不能提前free(iov_base)。 buf_index必须指向对应注册槽,buf和nbytes必须完全落在该槽范围内。- 注册内存会被锁定,并受
RLIMIT_MEMLOCK等资源限制影响;大池子可能注册失败。 - 完成队列里的
cqe->res小于 0 时,值本身就是负的 errno,不能再读取全局errno判断该次异步 I/O。 - 注销或替换缓冲区前,要明确哪些请求仍在使用旧槽位,避免把生命周期管理交给运气。
性能检查要比较什么
固定缓冲区减少的是每次 I/O 的内存映射与页引用管理成本,不会自动消除设备延迟、文件系统开销或队列设计问题。建议在同一块大小、队列深度、设备和读写比例下,对比普通 io_uring_prep_read() 与 io_uring_prep_read_fixed():
| 指标 | 要回答的问题 |
|---|---|
| IOPS / 吞吐 | 复用注册映射后是否真的增加完成量 |
| p50 / p99 延迟 | 高并发下尾延迟是否改善 |
| CPU 时间 | 每次 I/O 的内存管理开销是否下降 |
| 注册与注销耗时 | 一次性成本需要多少请求才能摊薄 |
| 锁定内存量 | 性能收益是否挤压了进程或系统内存预算 |
如果只提交几十次 I/O,注册成本很可能占据明显比例;如果缓冲池长期服务数百万次请求,固定映射才有机会体现价值。具体分界线由设备、内核、块大小和负载决定,不能用一个通用数字代替基准测试。
固定缓冲区和 provided buffer ring 不要混用概念
fixed buffer 的核心是:应用在提交请求时已经知道要用哪块已注册内存,因此明确给出 buf_index。provided buffer ring 则用于另一类任务:应用把一组空闲缓冲区交给 ring,请求设置 IOSQE_BUFFER_SELECT,等接收数据时由内核选择一个可用缓冲区,并通过 CQE 标记返回具体缓冲区 ID。

| 机制 | 提交时谁选缓冲区 | 典型任务 |
|---|---|---|
| 普通缓冲区 | 应用直接传地址 | 低频、一次性、动态内存 I/O |
| 固定缓冲区 | 应用传地址、长度和 buf_index | 高频文件或块 I/O、稳定缓冲池 |
| provided buffer ring | 内核从缓冲组中选择 | 网络接收、多请求等待可用接收缓冲区 |
常见问题
固定缓冲区等于零拷贝吗?
不等于。它优化的是用户内存注册、映射和页引用管理路径;具体 I/O 是否发生数据复制取决于操作类型、设备和协议,不能把 fixed buffer 直接宣传成所有场景的零拷贝。
固定缓冲区只能配合 O_DIRECT 吗?
不是。普通文件 I/O 也可以使用 fixed read/write,但官方文档特别指出它与 O_DIRECT 配合时很有价值。是否值得使用仍应由真实负载测量。
可以在一块注册内存中读取子区域吗?
可以。buf 不必等于注册区域起点,但 buf 到 buf + nbytes 必须完全位于 buf_index 指定的区域内。
缓冲池需要动态扩缩怎么办?
可以研究稀疏注册与 buffer update 接口,把空槽后续更新为真实缓冲区。但动态更新会增加资源与并发管理复杂度,若地址变化非常频繁,普通缓冲区或 provided buffer ring 可能更合适。
Python sqlite3 备份进度回调怎样判断剩余页数
- 上一篇
- Python sqlite3 备份进度回调怎样判断剩余页数
- 下一篇
- 对象仍被使用时 weak 指针为什么可能已经失效
-
- 文章 · linux | 5小时前 | Linux · memory.events Linux cgroup v2 memory.events.local cgroup内存事件 OOM定位
- Linux cgroup v2 的 memory.events.local 怎样区分本组事件
- 180浏览 收藏
-
- 文章 · linux | 7小时前 | Linux · 性能监控 · Pressure Stall Information poll Linux PSI POLLPRI 资源压力
- Linux PSI 触发器如何在压力超过阈值时通知进程
- 331浏览 收藏
-
- 文章 · linux | 9小时前 |
- systemd socket 的 Accept=yes 如何启动实例化服务
- 386浏览 收藏
-
- 文章 · linux | 11小时前 | Linux · 最小权限 StateDirectory systemd 服务 systemd DynamicUser 动态用户
- systemd DynamicUser 如何运行无固定账号的服务
- 261浏览 收藏
-
- 文章 · linux | 14小时前 | cgroup v2 memory.max Linux OOM OOM Killer oom_score
- OOM Killer 选择了哪个进程:分数、限制与证据收集
- 233浏览 收藏
-
- 文章 · linux | 17小时前 |
- ext4 与 XFS 在线扩容前后分别要核对什么
- 179浏览 收藏
-
- 文章 · linux | 19小时前 | 容器 · Linux · 容器隔离 mount namespace PID namespace linux namespace network namespace
- 用 namespace 理解容器进程、网络与挂载隔离
- 268浏览 收藏
-
- 文章 · linux | 21小时前 | Linux · 文件描述符 ·
- 日志轮转后服务仍写旧文件,文件描述符发生了什么
- 490浏览 收藏
-
- 文章 · linux | 23小时前 | 运维 · SSH Linux安全 sshd_config 服务加固 密钥登录
- SSH 只允许密钥登录后还要收紧哪些服务边界
- 297浏览 收藏
-
- 文章 · linux | 1天前 | Linux ·
- Linux 负载高但 CPU 空闲,怎样区分 I/O 等待与锁等待
- 398浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 386次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 468次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 475次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 415次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 241次使用
-
- go zero微服务实战性能优化极致秒杀
- 2022-12-27 207浏览
-
- Go pprof 排查慢接口:别只会看火焰图,先把问题问对
- 2026-06-01 101浏览
-
- Go JSON v2 实战:别急着替换 encoding/json,先搞懂这些变化
- 2026-06-01 437浏览
-
- Go 1.25 容器感知 GOMAXPROCS:K8s 里别再让 CPU limit 偷偷拖垮 P99
- 2026-06-01 473浏览
-
- Go map 新实现实战:Swiss Tables 变快了,但别急着改业务代码
- 2026-06-01 218浏览

