当前位置:首页>Linux>Linux下C语言写文件落盘:fflush/fsync正确用法以及经典坑点避坑指南

Linux下C语言写文件落盘:fflush/fsync正确用法以及经典坑点避坑指南

  • 2026-10-10 11:11:30
Linux下C语言写文件落盘:fflush/fsync正确用法以及经典坑点避坑指南
Hello,大家好,我是程序媛MM。

本文约2300字,最近好几款型号都要降内存,导致OTA升级模块因内存不足需要去调整写文件的策略,基于此本文再来梳理一次Linux下写文件确保数据落入物理盘的常规操作,但应用层全部合规操作,是否一定能确保数据写入物理盘呢?答案很显然不是,可参考之前遇到过的一次应用层操作合规依然写Flash失败的案例《【项目实战】SPI Nor Flash下OTA升级概率失败导致系统成砖问题的排查和解决方法》,应用层合规操作,到内核驱动,还得看驱动是否完全没有问题,否则还是可能数据不能完全写入物理盘(Flash),本文仅从Linux用户层来分析落盘的合规操作。

关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具,文末有文档清单。


前言

C 标准库文件操作存在用户态库缓冲区 + 内核页缓存两层缓存,很多开发者分不清 fflush、fsync、fclose 的作用,写出冗余代码、重复关闭文件、数据未落盘等问题。

本文结合缓冲模式分场景给出标准代码范例,同时梳理高频坑点,适用于日志、关键配置、持久化数据等需要保证数据落地磁盘的开发场景。

基础概念回顾

  1. 用户态缓冲区:FILE* 标准库自带,fwrite 优先写入此处,由 C 库管理。
  2. 内核页缓存:操作系统内核缓存,数据进入这里≠写入物理磁盘。
  3. 真正落盘:数据从内核页缓存刷入物理硬盘,断电不丢失。
  4. fflush:刷用户态缓冲区到内核缓存,不保证物理落盘。
  5. fsync:刷内核页缓存到物理磁盘,实现强制落盘。
  6. fclose:内部隐式调用 fflush,但不会调用 fsync。

一 场景分类与代码范例

场景一:默认缓冲模式(全缓冲/行缓冲)+ 要求强制物理落盘

这是最常用的业务场景:使用 fopen 默认缓冲,写入关键数据(交易、日志、配置),要求执行完毕数据必须落地磁盘。

核心规则

默认开启库缓冲 → 数据先存在用户态缓存;调用 fsync 前必须先执行 fflush,否则用户态残留数据无法落盘。

标准代码

#include<stdio.h>#include<unistd.h>#include<string.h>// 默认缓冲 + 强制落盘 标准实现intWriteFileWithSync(constchar *file_path, constvoid *data, size_t len){if (NULL == file_path || NULL == data || 0 == len)    {return-1;    }    FILE *fp = fopen(file_path, "ab+");if (NULL == fp)    {return-1;    }int ret = -1;size_t write_len = fwrite(data, 1, len, fp);// 校验写入结果if (write_len != len)    {goto END;    }// 1. 关键:默认缓冲必须先 fflush,清空用户态缓存到内核if (0 != fflush(fp))    {goto END;    }// 2. 获取文件描述符,调用 fsync 强制刷物理磁盘int fd = fileno(fp);if (-1 != fsync(fd))    {        ret = 0;  // 全部流程成功    }END:// 统一入口关闭文件,避免二次 close    fclose(fp);return ret;}

流程拆解

  1. fopen 打开文件,使用系统默认全缓冲;
  2. fwrite 写入数据到用户态库缓冲区;
  3. fflush:用户态缓存 → 内核页缓存;
  4. fsync:内核页缓存 → 物理磁盘(真正落盘);
  5. 统一 fclose 释放资源。

场景二:手动关闭缓冲(_IONBF 无缓冲)+ 要求强制物理落盘

手动通过 setvbuf 设置无缓冲模式,fwrite 数据会直接进入内核页缓存,不再经过用户态缓冲区。

核心规则

无缓冲模式下,fflush 完全无效、冗余,可直接省略,仅需 fsync 完成落盘。

标准代码

#include<stdio.h>#include<unistd.h>#include<string.h>// 无缓冲模式 + 强制落盘 标准实现intWriteFileNoBufferSync(constchar *file_path, constvoid *data, size_t len){if (NULL == file_path || NULL == data || 0 == len)    {return-1;    }    FILE *fp = fopen(file_path, "ab+");if (NULL == fp)    {return-1;    }// 关闭标准库用户态缓冲区    setvbuf(fp, NULL, _IONBF, 0);int ret = -1;size_t write_len = fwrite(data, 1, len, fp);if (write_len != len)    {goto END;    }// 无缓冲:跳过 fflush,直接 fsync 落盘int fd = fileno(fp);if (-1 != fsync(fd))    {        ret = 0;    }END:    fclose(fp);return ret;}

流程分析

  1. fopen + setvbuf(_IONBF):关闭用户态缓冲;
  2. fwrite 数据直接写入内核页缓存;
  3. 无需 fflush,直接调用 fsync 刷物理磁盘;
  4. 统一关闭文件流。

二 高频致命坑点

坑点1:无缓冲模式下调用 fflush(纯冗余,浪费系统调用)

错误示例

// 错误写法:无缓冲 + 多余 fflushsetvbuf(fp, NULL, _IONBF, 0);fwrite(buf, 1, len, fp);if (fflush(fp) != 0)  // 完全无用,多余调用{    fclose(fp);return-1;}fsync(fileno(fp));fclose(fp);

问题说明

_IONBF 代表无用户态缓冲,fflush 没有数据可刷,调用无任何作用,只会增加 IO 开销。

修复方案

直接删除fflush相关判断与调用。


坑点2:多处分支手动 fclose,最终重复关闭文件流(未定义行为)

错误示例

// 高危错误:二次 fcloseFILE *fp = fopen("test.txt", "ab+");fwrite(buf, 1, len, fp);if (fflush(fp) != 0){    fclose(fp);  // 分支内关闭return-1;}fsync(fileno(fp));fclose(fp);  // 再次关闭!二次 closereturn0;

问题说明

FILE* 被重复关闭属于 C 标准未定义行为,会引发野指针、程序崩溃、内存泄漏。

修复方案

使用goto统一资源释放入口,整段代码只保留一处 fclose。


坑点3:默认缓冲模式下,跳过 fflush 直接调用 fsync(数据丢失)

错误示例

// 严重错误:默认缓冲,跳过 fflushFILE *fp = fopen("test.txt", "ab+");fwrite(buf, 1, len, fp);// 直接 fsync,用户态缓存数据未进入内核,无法落盘fsync(fileno(fp));fclose(fp);

问题说明

数据还停留在用户态缓冲区,fsync只能操作内核缓存,无法读取用户态数据,断电后数据直接丢失。

修复方案

默认缓冲必须遵循:fwrite → fflush → fsync。


坑点4:误以为 fclose 可以替代 fsync(无法保证物理落盘)

很多人认为 fclose 会把数据刷到磁盘,这是典型误区。

  • fclose 只会隐式执行 fflush(数据进入内核缓存);
  • 不会调用 fsync,内核缓存依旧由操作系统后台异步刷盘。
  • 场景:断电、系统崩溃时,内核缓存数据会丢失。

适用区分:

  • 普通临时文件、非核心数据:只用 fwrite + fclose 即可;
  • 关键持久化数据:必须手动调用 fsync。

三 fflush/fsync调用规则总结

1. 什么时候需要 fflush

✅ 需要调用

  1. FILE* 使用默认缓冲,且后续要执行 fsync 强制落盘;
  2. 写入数据后,立刻在同个流上执行读取操作。

❌ 不需要调用

  1. 已通过 setvbuf(_IONBF) 设置无缓冲;
  2. 仅正常读写,最终直接 fclose(fclose 内部自动 fflush)。

2. 什么时候需要 fsync

✅ 需要调用

  1. 数据为关键持久化数据(日志、配置、交易数据),要求断电不丢失;
  2. 多进程共享文件,需要其他进程立刻看到落地数据。

❌ 不需要调用

  1. 临时文件、缓存文件、日志回放类非核心数据;
  2. 可接受操作系统后台异步刷盘(绝大多数普通业务)。

3. 极简使用口诀

  1. 默认缓冲 + 强制落盘:先 fflush,再 fsync
  2. 无缓冲 + 强制落盘:直接 fsync,不用 fflush
  3. 无需强制落盘:只写 fwrite + fclose,两者都不用
  4. 资源释放:全局统一一处 fclose,严禁多处关闭

四 补充拓展

  1. fdatasync:功能类似 fsync,仅同步文件数据、不同步文件元数据,性能更高,对纯数据文件可优先使用;
  2. fileno:非标准 C 库函数,Linux/UNIX 系支持,跨平台场景需谨慎使用;
  3. 分块写入场景:文中两套范例均可适配循环分块 fwrite,规则保持不变。

📌 总结一句话:默认缓冲先 fflush 再 fsync,无缓冲直接 fsync,资源释放只关一次 fclose。

往期文章(欢迎订阅技术分享栏目全部文章):


【从零开始撸内核驱动源码】:以ttyserial(串口驱动)为例,串联字符设备驱动基础知识点的学习计划
Linux内核源码顶层 Makefile分析并单独编译调试内核自带的驱动
【从零开始撸内核驱动源码】:ttynull驱动
Linux内核驱动安装失败问题调试及解决方法
Linux内核驱动源码走读之编译内核及外部驱动实操指南

“谢谢你看到这里”

这里是女程序员的笔记本

 15年+嵌入式软件工程师兼二胎宝妈

分享读书心得、工作经验,自我成长和生活方式。

希望我的文字能对你有所帮助

最新文章

随机文章