Linux下使用O_DIRECT写入非块对齐文件的Libc调用异常问题
问题原因分析
- Libc stdio缓冲区的异步特性:
fwrite不会直接将数据写入磁盘,而是先存入用户态的stdio缓冲区。当你写完尾部数据后直接修改文件描述符的O_DIRECT标志,缓冲区中的尾部数据还未刷入内核,后续操作会因为O_DIRECT的约束导致缓冲区数据被丢弃。 - O_DIRECT与stdio缓冲区的兼容性冲突:O_DIRECT要求IO操作的缓冲区、数据大小、文件偏移都严格对齐块大小(512字节),而stdio的缓冲区设计不满足该约束。当你重新开启O_DIRECT后,stdio的后续操作(包括隐式的
fflush或文件关闭时的刷写)会因为对齐失败而无法写入尾部数据,最终导致文件仅保留了之前写入的完整块数据。 - 统计值的误导:
fwrite的返回值统计的是写入缓冲区的字节数,而非实际写入磁盘的字节数,这就是为什么程序输出统计为1050625字节,但实际文件大小不符。
解决方法
方法1:强制刷写缓冲区并同步磁盘
在写入尾部数据后、重新开启O_DIRECT之前,执行以下步骤确保数据落地:
- 调用
fflush(fp)将stdio缓冲区的数据刷入内核空间; - 调用
fsync(fileno(fp))将内核中的数据同步到磁盘; - 再修改文件描述符的O_DIRECT标志。
示例代码片段:
// 写入尾部数据 size_t tail_size = 1050625 - 1048576; fwrite(tail_data, 1, tail_size, fp); // 强制刷写并同步磁盘 fflush(fp); fsync(fileno(fp)); // 重新开启O_DIRECT int flags = fcntl(fileno(fp), F_GETFL); fcntl(fileno(fp), F_SETFL, flags | O_DIRECT);
方法2:拆分FILE*操作,避免混合模式
关闭当前的FILE*,重新以非O_DIRECT模式打开文件写入尾部数据,再关闭后按需重新开启O_DIRECT,彻底规避模式冲突:
// 关闭原FILE* fclose(fp); // 以非O_DIRECT模式打开,写入尾部数据 int fd = open("test_file", O_WRONLY | O_APPEND); write(fd, tail_data, tail_size); fsync(fd); close(fd); // 按需重新以O_DIRECT模式打开文件 fp = fopen("test_file", "r+"); int flags = fcntl(fileno(fp), F_GETFL); fcntl(fileno(fp), F_SETFL, flags | O_DIRECT);
方法3:绕开stdio,直接用系统调用写尾部数据
通过fileno(fp)获取文件描述符后,关闭O_DIRECT直接用write系统调用写入尾部数据,完全避开stdio缓冲区的干扰:
// 关闭O_DIRECT标志 int flags = fcntl(fileno(fp), F_GETFL); fcntl(fileno(fp), F_SETFL, flags & ~O_DIRECT); // 用系统调用写入尾部数据并同步 write(fileno(fp), tail_data, tail_size); fsync(fileno(fp)); // 重新开启O_DIRECT标志 flags = fcntl(fileno(fp), F_GETFL); fcntl(fileno(fp), F_SETFL, flags | O_DIRECT);
内容的提问来源于stack exchange,提问作者Shikhar
相关产品推荐
相关产品推荐

