固定缓冲能避免内存拷贝,是因为预先注册的页对齐内存被内核锁定并建立持久dma映射,i/o时内核直接与该物理内存交换数据,绕过copy_to_user/copy_from_user;注册需调用io_uring_register_buffers传入iovec数组,提交时sqe->buf_index指定缓冲区编号、addr设为偏移量,并置iosqe_buffer_select标志。

为什么固定缓冲能避免内存拷贝?
默认情况下,io_uring 提交的读写请求使用用户提供的临时缓冲区(如栈上 char buf[4096]),内核必须在每次 I/O 时将数据从内核空间复制到用户缓冲区(或反之)。启用固定缓冲后,你提前注册一段物理连续、页对齐的内存区域,内核可直接 DMA 读写——绕过 copy_to_user / copy_from_user。关键前提是:这段内存必须用 memalign(4096, size) 或 posix_memalign 分配,并且整个 buffer ring 生命周期内不能 free 或重映射。
如何注册固定缓冲并绑定到 sqe?
注册分两步:先调用 io_uring_register_buffers 把缓冲区数组交给内核,再在每个 io_uring_sqe 中用 addr 指向缓冲区内偏移、用 buf_index 指定缓冲区编号(从 0 开始)。注意:buf_index 不是地址,而是你注册时传入的 iovec 数组下标。
- 分配缓冲区必须页对齐:
posix_memalign(&buf, 4096, 65536) - 构造
iovec数组:struct iovec iov = { .iov_base = buf, .iov_len = 65536 }; - 注册:
io_uring_register_buffers(&ring, &iov, 1)(第二个参数是iovec*,第三个是数量) - 提交读请求时设置:
sqe->addr = 0(相对起始偏移),sqe->buf_index = 0,sqe->flags |= IOSQE_BUFFER_SELECT
常见错误:缓冲区未对齐或注册失败后仍强行提交
最典型的报错是提交后 cqe->res == -EINVAL,原因几乎全是:buf_index 超出注册数量、addr + len 越界、或分配的内存没对齐。调试时先检查 io_uring_register_buffers 返回值——它只在第一次调用时生效,后续重复调用会返回 -EBUSY;若注册失败却继续提交带 IOSQE_BUFFER_SELECT 的 sqe,内核直接拒绝。
- 用
getpagesize()确认对齐值,不要硬写4096(某些 ARM 平台是 64K) - 注册前打印
(uintptr_t)buf & (getpagesize()-1),确保为 0 -
buf_index必须,越界不会报 warning,只会静默失败 - 固定缓冲一旦注册,就不能
munmap或free对应内存,否则后续 I/O 可能触发 page fault 或数据损坏
固定缓冲的性能代价和适用场景
固定缓冲省掉了 memcpy,但引入了额外约束:所有 I/O 必须复用同一块大内存,需自行管理内部 offset 和长度;而且注册本身有开销(尤其多缓冲时),不适合短生命周期应用。它最适合长连接、高吞吐、固定包长的场景(如 HTTP/2 frame 解析、自定义协议收发)。
- 单缓冲大小建议 ≥ 最大单次 I/O 长度(例如 128KB),避免频繁切片
- 如果业务需要同时处理多种长度请求(如小 ping 包 + 大文件块),考虑注册多个不同尺寸的
iovec,用不同buf_index区分 - 固定缓冲不兼容
IORING_OP_READ_FIXED以外的 op(比如IORING_OP_WRITEV不能混用),必须统一走_FIXED系列指令
真正难的不是注册那几行代码,而是把业务逻辑适配进“一块大内存 + 自己切片”的模型里——buffer 管理逻辑很容易变成新的瓶颈点。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











