必须加o_direct才启用direct io;需同时满足open时指定o_direct、缓冲区地址对齐、文件偏移和读写长度均为逻辑块大小整数倍,三者缺一不可。

Linux 下 open() 必须加 O_DIRECT 标志才启用 Direct IO
不加 O_DIRECT,哪怕你用 posix_memalign 分配对齐内存、绕过 fread 改用 read/write,系统仍会走 page cache —— 这是常见误解。Direct IO 是内核强制的路径切换,不是用户态“绕开”就能生效的。
关键约束有三个:
-
open()时必须传入O_DIRECT(不能事后用fcntl补) - 缓冲区地址必须按硬件扇区对齐(通常是 512B 或 4KB),推荐用
posix_memalign(&buf, 4096, size)分配 - I/O 偏移量和长度都必须是逻辑块大小的整数倍(
lseek到非对齐 offset 后调read会直接失败并返回-1,errno = EINVAL)
read() 和 write() 在 Direct IO 下的对齐错误表现
最常踩的坑是只对齐了缓冲区,却忽略了文件 offset 和 length。比如:
char *buf;
posix_memalign(&buf, 4096, 65536);
int fd = open("/bigfile.bin", O_RDWR | O_DIRECT);
lseek(fd, 1000, SEEK_SET); // ← 错!offset=1000 不是 4096 的倍数
read(fd, buf, 65536); // ← 立即失败,errno=EINVAL
正确做法是手动对齐 offset 和 length:
- 用
off_t aligned_off = (off_t)((uintptr_t)orig_off & ~(4095));向下对齐到 4KB 边界 - 读取长度需补足(例如你想读 1000 字节,但必须读 4096,再从 buffer 中取有效段)
- 实际业务中建议以 4KB 为最小 I/O 单位,避免频繁计算对齐
为什么 O_DIRECT 有时比缓存 I/O 还慢?
Direct IO 不是银弹。它跳过 page cache,但也失去了以下优化:
- 内核无法合并相邻小请求(如连续 4 次 1KB
write会被发成 4 次磁盘命令) - 没有预读(
readahead)机制,顺序读大文件时吞吐可能反降 - 每次系统调用都触发真实磁盘 I/O,上下文切换开销更重
适用场景很明确:你已确认瓶颈在 page cache 内存竞争(比如多进程争抢 cache 导致 swap),或需要精确控制数据落盘时机(如数据库 WAL 写入)。普通日志写入或备份工具,O_SYNC + 缓存 I/O 往往更稳更快。
C++ 封装 Direct IO 时别碰 std::fstream
std::fstream 完全不支持 O_DIRECT —— 它底层调用的是带缓冲的 fopen/fread,且缓冲区无法保证地址对齐。强行用它做 Direct IO,轻则静默退化为缓存 I/O,重则触发 SIGBUS(尤其在 mmap + O_DIRECT 混用时)。
可靠做法只有两个:
- 坚持用 POSIX 接口:
open/read/write/close,配合posix_memalign - 若需 RAII,自己封装一个
DirectFile类,把 fd、buffer、size 全部管理起来,禁止拷贝,只允许移动
对齐检查和错误码处理必须嵌进每个 I/O 调用里,errno == EINVAL 不是异常,是流程的一部分。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











