土法炼钢兴趣小组的算法知识备份

【存储工程】O_DIRECT 与 io_uring:固定缓冲区、register_buffers 与工程选型

文章导航

分类入口
storage
标签入口
#o-direct#io-uring#liburing#direct-io#register-buffers#fixed-buffers#database-io

源码下载

本文相关源码已整理,共 2 个文件。

打开下载目录 →

目录

数据库和块存储引擎常同时遇到两件事:O_DIRECT 绕过 Page Cache 避免双重缓冲(见 #10 Direct I/O),以及 用 io_uring 降低 syscall 开销(见 io_uring 内核内部)。两者可以组合,但对齐、posix_memalignio_uring_register_buffersIOSQE_FIXED_FILE 各有一层约束。本文把组合路径、实测数据和选型边界写清楚。

测试环境说明 下文 benchmark 在 Linux 6.6.87(WSL2)、liburing 2.14、ext4 文件系统、/tmp 上实测。块设备裸盘与 NVMe 上的绝对 IOPS 会更高;结论侧重路径差异与约束,不跨环境比绝对数值。


一、为什么要把两者放在一起

flowchart LR
    APP[应用 Buffer Pool]
    UR[io_uring SQ/CQ]
    VFS[VFS + 文件系统]
    PC[Page Cache]
    DISK[块设备]
    APP -->|O_DIRECT 路径| UR --> VFS --> DISK
    APP -->|缓冲 I/O| UR --> VFS --> PC --> DISK
路径 数据副本 典型用户
缓冲 I/O + Page Cache 用户缓冲 + 页缓存 通用文件、小文件
O_DIRECT 仅用户缓冲(对齐后直写块层) InnoDB 数据文件、Ceph OSD、自定义引擎
O_DIRECT + io_uring 同上 + 批量提交减少 syscall 高 IOPS 数据库、NVMe 压测工具

O_DIRECT 解决缓存语义;io_uring 解决提交效率。互不替代。


二、O_DIRECT 硬约束(复习)

来自 #10,组合 io_uring 前必须满足:

  1. 缓冲区地址、长度、文件偏移 均按逻辑块大小对齐(ext4 常见 4 KB)。
  2. 使用 posix_memalign(4096, size)mmap 对齐映射,不要用普通 malloc
  3. 文件系统与块层需支持 direct I/O;部分网络文件系统不支持或行为怪异。
  4. O_DIRECTmmap 同一文件混用会踩坑;sendfile 等页缓存路径与 direct fd 不兼容。
void *buf;
posix_memalign(&buf, 4096, 4096);
int fd = open(path, O_RDWR | O_CREAT | O_TRUNC | O_DIRECT, 0644);

三、io_uring 固定缓冲区:io_uring_register_buffers

3.1 作用

每次 read/write 传统路径需要 pin 用户页。io_uring_register_buffers() 预先注册一组 iovec,内核长期持有映射,后续 SQE 可通过 buf_index 引用,减少 per-I/O 页表开销。

struct io_uring ring;
io_uring_queue_init(256, &ring, 0);

struct iovec iov = { .iov_base = aligned_buf, .iov_len = 4096 };
io_uring_register_buffers(&ring, &iov, 1);

struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_write(sqe, fd, NULL, 4096, offset);
sqe->buf_index = 0;   /* 使用注册缓冲区槽位 0 */
io_uring_submit(&ring);

IORING_REGISTER_FILES(固定 fd 槽位)独立:前者优化缓冲区 pin,后者优化 fd 查找

3.2 与 O_DIRECT 叠加时的注意点

3.3 Provided Buffers(进阶)

IOSQE_BUFFER_SELECT + io_uring_setup_buf_ring 是更新的缓冲池模型(见 io_uring 高级特性),适合网络收包;块设备 Direct I/O 场景更常见仍是 register_buffers 或每请求显式缓冲区。


四、实测:三种 io_uring 写路径(10k × 4KB)

测试程序对同一 ext4 文件顺序写 10000 次 4 KB(约 40 MB),对比:

  1. 缓冲 I/O + io_uring(无 O_DIRECT
  2. O_DIRECT + io_uring(对齐缓冲,无 register)
  3. O_DIRECT + register_buffers(本文环境失败)
buffered io_uring write:     0.284 s, ~35242 IOPS
O_DIRECT io_uring write:     0.271 s, ~36840 IOPS
O_DIRECT + register_buffers: EFAULT(未成功完成)

解读(限于本环境):

完整复现可参考 liburing 示例 examples/reg-wrman io_uring_register_buffers


五、与 epoll / 同步 read 的选型

场景 更常见选择 原因
数据库 16KB 页随机读写 O_DIRECT + 同步或 libaio/io_uring 缓存语义由 Buffer Pool 掌控
高并发网络 + 小包 epoll 或 io_uring(常非 Direct) 延迟与连接管理优先
块设备裸盘压测 O_DIRECT + io_uring/libaio,direct=1 #64 基准方法论
大量空闲连接 epoll 可能更省(见 io_uring vs epoll io_uring 每连接状态成本

io_uring 不是 epoll 的完全替代;O_DIRECT 也不是所有存储栈的默认。组合用于:已决定绕过页缓存,且需要批量异步提交


六、工程检查清单

□ 缓冲区 4K(或设备块大小)对齐
□ open 带 O_DIRECT;ftruncate 预分配避免 ENOSPC 中途失败
□ 文件系统 block size 与 I/O 大小兼容
□ io_uring register_buffers 在目标内核实测通过
□ 监控 iowait 与 Buffer Pool 命中率,确认没有「双缓存」
□ 与 fsync/fsync 策略一致(见 #12 数据完整性)

七、与系列文章的衔接


参考资料


复现 benchmark:examples/storage/79-o-direct-io-uring/make && make run

上一篇: 存储事故复盘 相关: io_uring 内核内部 · Direct I/O 详解 · Linux 异步 I/O

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-08-19 · storage

【存储工程】Direct I/O 与 O_DIRECT:绕过缓存的得与失

在 Linux 的传统 I/O 路径中,应用程序通过 read() 和 write() 系统调用与文件交互时,数据并不会直接在用户空间缓冲区(User Buffer)和磁盘之间传输。内核会在两者之间插入一层页缓存(Page Cache),作为磁盘数据在内存中的缓存副本。一次典型的写入流程如下:

2025-08-20 · storage

【存储工程】Linux 异步 I/O:从 POSIX AIO 到 io_uring

在高吞吐存储系统中,同步 I/O 是性能的天花板。本文系统梳理 Linux 下三代异步 I/O 方案——POSIX AIO、Linux Native AIO(libaio)和 io_uring——的设计原理、编程接口、性能特征与工程实践,帮助你在实际项目中做出正确的技术选型。


By .