fflush和fsync之间的区别

73
我以为fsync()会在内部执行fflush(),所以在流上使用fsync()是可以的。但是当在网络I/O下执行时,我得到了一个意外的结果。
我的代码片段:
FILE* fp = fopen(file, "wb");
/* multiple fputs() calls like: */
fputs(buf, fp);
...
...
fputs(buf.c_str(), fp);
/* get fd of the FILE pointer */
fd = fileno(fp);
#ifndef WIN32
ret = fsync(fd);
#else
ret = _commit(fd);
fclose(fp);

但是似乎_commit()没有刷新数据(我在Windows上尝试过,数据被写入了一个Linux导出的文件系统)。

当我将代码更改为:

FILE* fp = fopen(file, "wb");
/* multiple fputs() calls like: */
fputs(buf, fp);   
...   
...
fputs(buf.c_str(), fp);
/* fflush the data */
fflush(fp);
fclose(fp);

这会刷新数据。

我想知道 _commit() 是否和 fflush() 做的是同样一件事。有什么输入吗?


第一个示例中您遇到的问题是什么? - rogerdpack
1
在第一个示例中,通过fputs()向流写入数据即使在fd(文件描述符)上调用_commit()函数也不会同步/提交到磁盘。这个测试是在集群系统下进行的,远程Linux文件系统被导出为CIFS并在Windows机器上使用,并且在写入过程中进行了节点故障转移的测试。当节点恢复时,发现文件大小为零。 - Adil
#endif在哪里? - binki
1
问题在第五次修订之前是内部一致的,答案也与之一致。第五次修订根本改变了问题的性质。回滚到第四次修订。 - John Bollinger
6个回答

111

fflush() 作用于 FILE*,它将你的应用程序中 FILE* 的内部缓冲区刷新到操作系统。

fsync 则作用于更低层级,它告诉操作系统将其缓冲区刷新到物理介质上。

操作系统会强烈缓存写入文件的数据。如果操作系统每次都要将数据写入磁盘,速度会非常慢。 fsync (以及其他一些方式)允许你控制何时将数据写入磁盘。

此外,fsync/commit 作用于文件描述符。它不了解 FILE* 并且无法刷新其缓冲区。 FILE* 存在于你的应用程序中,而文件描述符则存在于操作系统内核中。


谢谢,我也是这么想的。所以,如果我们正在使用FILE*,那么可以通过fflush()和fsync()实现相同的效果。 - Adil
不行,因为你无法对FILE*执行fsync操作。 - pattivacek
8
你可以使用 stdio.h 中的函数 int fileno(FILE *stream) 来从 FILE * 中获取文件描述符。 - jotik
2
@jotik:你应该使用 要么(标准)FILE *函数,要么(操作系统)文件句柄。不要混用它们。fileno() 不是标准函数。不幸的是,人们一直对“扩展”标准头文件非常懈怠... - DevSolar
4
fileno() 是 POSIX 标准的一部分,因此尽管它不一定可移植,在某些平台上它是标准的。 - Josh Kelley

12
标准的C函数fflush()和POSIX系统调用fsync()在概念上有些相似。 fflush()作用于C文件流(FILE对象),因此是可移植的。而fsync()则针对POSIX文件描述符操作。两者都会导致缓冲数据被发送到目标位置。
在POSIX系统中,每个C文件流都有一个相关联的文件描述符,并且所有对C文件流的操作将通过委托到必要时操作文件描述符的POSIX系统调用来实现。

有人可能认为在POSIX系统上调用fflush会导致文件流缓冲区中的任何数据被write,然后针对该文件流的文件描述符调用fsync()。因此,在POSIX系统上,调用fflush后不需要再调用fsync(fileno(fp))。但情况是这样吗:从fflushfsync是否存在调用?

不,调用POSIX系统上的fflush并不意味着将调用fsync

fflush的C标准规定(强调添加):

导致[流]的任何未写入数据传递给主机环境被写入文件

说数据是“将要”被写入,而不是已经被写入,意味着主机环境可以进一步进行缓冲。这种由“主机环境”进行的缓冲可能包括 POSIX 环境中,fsync 刷新的内部缓冲。因此,对 C 标准的仔细阅读表明,该标准并不要求 POSIX 实现调用 fsync

POSIX 标准描述 fflush 没有声明 fsync 被调用作为 C 语言语义的扩展


3

fflush()fsync()可用于尝试确保数据被写入存储介质(但并不总是可能):

  1. 首先,在输出流上使用fflush(fp)(其中fp是从fopen或标准流stdoutstderr获取的FILE *),将与流相关联的缓冲区内容写入操作系统。
  2. 然后,使用fsync(fileno(fp))告诉操作系统将其自己的缓冲区写入存储介质。

需要注意的是,fileno()fsync()是POSIX函数,并非所有系统都可用,特别是在Microsoft旧系统中,可能会有替代函数_fileno()_fsync()_commit()


1
这是唯一一个解释完整过程的答案,需要调用fflushfsync,所以我最喜欢这个答案。 - user8128167

2

为简单起见,我可以这么说:

对于非流文件(整数文件描述符),请使用 fsync()

对于文件流,请使用 fflush()

此外,这里是来自man的帮助:

int fflush(FILE *stream); // flush a stream, FILE* type

int fsync(int fd); // synchronize a file's in-core state with storage device
                    // int type

那么 fflush() 是否会隐式地为您调用 fsync() 呢? - binki
2
@binki 不是的 - 它在缓冲数据上调用 write() - Guillaume
啊,看起来问题被编辑成了错误的内容,而在我查看之后进行了修正(当我查看时,问题是“fflush()是否会为您调用fsync()?”,现在又回到了荒谬的“fsync()是否会为您调用fflush()”,这是不可能的,因为您可以将FILE*映射到fd,但不能将fd映射到FILE*)。所以@Guillaume,你是说需要先执行fflush(),然后再执行fsync()(<del>除非您即将执行fclose(),它会自动为您完成两个操作</del>,哦,fclose()不会自动刷新,xD)。 - binki
如果您想确保所有写入的数据(由缓冲和非缓冲I/O编写)都在支持文件的物理介质上,那么您需要同时调用 fflush()fsync()。一般来说,关心这个问题的人不使用缓冲I/O(例如使用 setvbuf()),因此他们通常只执行 fsync() - Guillaume

1
为了将最近的更改提交到磁盘,使用sync()或fsync()函数。
fsync()会将给定文件的所有数据和元数据与永久存储设备同步。应该在相应的文件被关闭之前调用它。
sync()会将所有已修改的文件提交到磁盘。

1
我们正在讨论fsync和fflush之间的区别。 - Bhupesh Pant

1
我认为来自Python文档(https://docs.python.org/2/library/os.html)的以下文档非常清楚地阐明了此问题。

os.fsync(fd) 强制将带有文件描述符fd的文件写入磁盘。 在Unix上,这会调用本地的fsync()函数; 在Windows上,这是MS _commit() 函数。

如果您从Python文件对象f开始,请先执行f.flush(),然后执行os.fsync(f.fileno()),以确保将与f相关联的所有内部缓冲区都写入磁盘。

可用性:Unix和2.2.3版之后的Windows。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接