You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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之前,执行以下步骤确保数据落地:

  1. 调用fflush(fp)将stdio缓冲区的数据刷入内核空间;
  2. 调用fsync(fileno(fp))将内核中的数据同步到磁盘;
  3. 再修改文件描述符的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:32:47