如何强制std::fstream直接从磁盘读写而非磁盘缓存?
嘿,这个需求在测试类数据库应用的持久化可靠性时太常见了!我给你整理几个简便的实现方式,不用大动现有代码:
方法一:直接修改文件描述符启用无缓存IO(绕过系统页缓存)
std::fstream底层依赖系统的文件描述符,我们可以通过系统调用修改这个描述符的属性,强制启用直接IO(绕过系统缓存,直接读写磁盘)。不过这个方案需要分平台处理:
Linux/Unix 平台
利用fcntl给文件描述符加上O_DIRECT标志,注意要满足磁盘对齐要求(缓冲区、读写大小、文件偏移都要对齐到磁盘块大小,否则会报错):
#include <fstream> #include <cstdio> #include <fcntl.h> #include <unistd.h> int main() { std::fstream file("data.db", std::ios::in | std::ios::out | std::ios::binary); if (!file.is_open()) { // 处理打开失败 return 1; } // 获取底层文件描述符 int fd = fileno(file.rdbuf()->file()); if (fd == -1) { // 处理错误 return 1; } // 启用直接IO int current_flags = fcntl(fd, F_GETFL); if (fcntl(fd, F_SETFL, current_flags | O_DIRECT) == -1) { // 处理设置失败(比如权限不足) return 1; } // 后续读写操作就会直接绕过系统缓存 // 注意:缓冲区要对齐,比如用posix_memalign分配 char* buf; size_t block_size = sysconf(_SC_BLOCK_SIZE); // 获取磁盘块大小 posix_memalign((void**)&buf, block_size, block_size * 4); // 用buf进行读写... free(buf); return 0; }
Windows 平台
Windows下需要给文件句柄设置FILE_FLAG_NO_BUFFERING和FILE_FLAG_WRITE_THROUGH标志,同样要满足对齐要求:
#include <fstream> #include <io.h> #include <windows.h> int main() { // 先通过CreateFile直接打开带无缓存标志的文件 HANDLE hFile = CreateFileA( "data.db", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, NULL ); if (hFile == INVALID_HANDLE_VALUE) { // 处理打开失败 return 1; } // 将系统句柄转为C标准库的FILE*,再包装成std::fstream int fd = _open_osfhandle((intptr_t)hFile, _O_RDWR | _O_BINARY); FILE* fp = _fdopen(fd, "rb+"); std::fstream file(fp); // 后续操作file即可直接读写磁盘 // 注意:缓冲区要对齐到磁盘扇区大小(通常512字节或4KB) char buf[4096] = {0}; // 假设扇区大小4KB file.read(buf, 4096); return 0; }
方法二:每次操作后强制刷盘(测试场景首选)
如果你的测试只是想确保数据真的写入磁盘,而不是完全绕过缓存,这个方案更简便,不需要处理烦人的对齐问题:
核心思路是先刷新C++标准库的缓存,再调用系统API把系统缓存刷到磁盘:
Linux/Unix 平台
#include <fstream> #include <cstdio> #include <unistd.h> void write_and_flush(std::fstream& file, const std::string& data) { file << data; file.flush(); // 把C++库缓存刷到系统缓存 int fd = fileno(file.rdbuf()->file()); fsync(fd); // 把系统缓存强制刷到磁盘(fdatasync()更快,只刷数据不刷元数据) }
Windows 平台
#include <fstream> #include <io.h> #include <windows.h> void write_and_flush(std::fstream& file, const std::string& data) { file << data; file.flush(); // 刷新C++库缓存 HANDLE hFile = (HANDLE)_get_osfhandle(fileno(file.rdbuf()->file())); FlushFileBuffers(hFile); // 强制刷到磁盘 }
这个方案改动极小,只需要在关键写操作后调用这个刷盘函数,完全适配你现有的std::fstream代码,非常适合测试场景。
注意事项
- 直接IO(方法一)的性能不一定更好,因为系统缓存本来就是为了加速IO设计的,测试完记得改回正常模式;
- 直接IO的对齐要求必须满足,否则会触发系统错误,Linux下可以用
sysconf(_SC_BLOCK_SIZE)获取对齐大小,Windows下用GetDiskFreeSpace获取扇区大小; - 方法二的
fsync/FlushFileBuffers是同步操作,会阻塞直到数据写入磁盘,测试时要注意性能影响。
内容的提问来源于stack exchange,提问作者Calypso
相关产品推荐
相关产品推荐

