在close(2)之后调用fsync(2)

3

场景:

任务代码(省略错误检查):

// open, write and close
fd = open(name);
write(fd, buf, len);
close(fd);
< more code here **not** issuing read/writes to name but maybe open()ing it >
// open again and fsync
fd = open(name);
fsync(fd);

系统中不再有任务同时访问name

它是否已定义,并且更重要的是,它是否会同步引用name的inode上可能存在的未完成写操作?也就是说,在执行fsync后,我是否可以从文件中读回buf

根据POSIX http://pubs.opengroup.org/onlinepubs/009695399/functions/fsync.html,我认为这似乎是合法的...

谢谢。

编辑于5月18日: 感谢您的答案和研究。我在2016年将这个问题提交给了extfs的主要开发人员之一(Ted),并得到了这个答案:“这不是由Posix保证的,但在实践中,它应该适用于大多数文件系统,包括ext4。Posix规范中的关键字是:

fsync()函数应请求将fildes命名的打开文件的所有数据传输到与fildes描述的文件相关联的存储设备。

它并没有说“所有与fildes描述的文件相关的数据……”,而是说“所有打开的文件描述符的数据”。因此,从另一个文件描述符写入的数据在技术上不能保证同步到磁盘。
实际上,文件系统不会尝试通过哪个fd来脏数据,所以您不需要担心。如果操作系统写入比严格要求更多的内容,则符合标准,因此即使没有保证,通常也会发现这种情况。这比“完全相同的持久性保证”不太具体,但非常权威,尽管可能已过时。
我想要做的是一个针对单个文件的“同步”命令,例如在shell脚本中使用时,可以像fsync /some/file一样工作,而不必同步整个文件系统。 现在(几年前开始),gnu coreutils的“sync”可以在单个文件上工作,并且确实可以执行此操作(打开/fsync)。提交记录:https://github.com/coreutils/coreutils/commit/8b2bf5295f353016d4f5e6a2317d55b6a8e7fd00

由于您刚刚打开文件描述符,因此没有“要传输到fildes命名的打开文件描述符的数据”,所以我会说不。 - marcolz
@marcolz 这就是我的疑问。但我会说,filedes所引用的inode上的挂起请求是“打开文件描述符的数据”。 - jneighb
这条评论表明,你可以“写入一堆文件而不需要fsync,然后重新打开/fsync它们”,这意味着在关闭后进行fsync是有效的。这样做是有道理的——否则,在close()之后就没有办法强制将数据写入磁盘了。但是最好能够得到一个权威的答案。我已经开始了一个悬赏。 - nh2
我在linux-fsdevel上提出了这个问题。我得到确认,至少在写作时,close()/re-open()/fsync()不能像fsync()/close()一样提供相同的保证。 - nh2
2个回答

6
No, close()+重新open()+fsync()不能提供与fsync()+close()相同的保证。

来源:我提出了这个问题linux-fsdevel邮件列表中,然后得到了答案

一系列的close()/re-open()/fsync()操作能否提供与fsync()/close()相同的耐用性保证?

简短的答案是否定的,后者提供更好的保障。更长的答案是,耐久性保证取决于内核版本,因为在v4.13、v4.14以及现在的v4.17-rc和稳定内核中情况一直在变化。

进一步相关链接如下:

特别是后面的链接描述了如何

  • 关闭FD后,您将失去所有确保持久性的方式
  • 如果fsync()失败,您不能再次调用fsync()以期望现在您的数据已写入
  • 如果发生这种情况,您必须重新执行/确认所有写入工作

谢谢回答。我已经更新了我的问题并添加了一些信息。 - jneighb

4
当前(2017年)的POSIX规范fsync()识别基本功能和可选功能:

fsync()函数将请求传输由fildes命名的打开文件描述符的所有数据到与fildes描述的文件相关联的存储设备。传输的性质是实现定义的。 fsync()函数将在系统完成该操作或检测到错误之前不返回。

[SIO] ⌦ 如果定义了_POSIX_SYNCHRONIZED_IO,则fsync()函数将强制执行与文件描述符fildes指示的文件相关的所有当前排队的I/O操作进入同步I/O完成状态。所有I/O操作都应按照同步I/O文件完整性完成的定义完成。 ⌫

如果实现未定义_POSIX_SYNCHRONIZED_IO,则重新打开的文件描述符没有未写入的数据需要传输到存储设备,因此fsync()调用实际上是无操作。

如果实现定义了_POSIX_SYNCHRONIZED_IO,则重新打开的文件描述符将确保将写入与要传输到存储设备的文件相关联的任何文件描述符上的所有数据。

标准中关于一致性的部分提供了有关选项和选项组的信息。 定义部分包含有关同步I/O和同步I/O方面的定义382..387(是的,它们是不同的,请注意打开的文件描述符和打开的文件描述符)。 实时部分推迟到定义部分以说明同步I/O的含义。

它定义:

3.382 同步输入和输出

一种提高数据输入和输出机制的确定性和鲁棒性的机制,使应用程序能够确保正在操作的数据物理上存在于辅助存储设备上。

3.383 同步I/O完成

一个I/O操作已经成功传输或被诊断为不成功的状态。

3.384 同步I/O数据完整性完成

对于读取,当操作已经完成或者被诊断为不成功时。只有在数据的图像成功地传输到请求进程时,读取才算完成。如果在请求同步读取操作时有任何待处理的写入请求影响要读取的数据,则这些写入请求会在读取数据之前成功传输。

对于写入,当操作已经完成或者被诊断为不成功时。只有在成功传输了写入请求中指定的数据以及检索数据所需的所有文件系统信息后,写入才算完成。

不需要成功传输的文件属性(访问时间、修改时间、状态更改时间)在返回给调用进程之前不需要成功传输。

3.385 同步I/O文件完整性完成

与同步I/O数据完整性完成相同,但是在返回给调用进程之前,与I/O操作相关的所有文件属性(包括访问时间、修改时间、状态更改时间)都需要成功传输。

3.386 同步I/O操作

在文件上执行的I/O操作,为应用程序提供其数据和文件完整性的保证。

3.387 同步I/O操作

导致请求I/O的线程被阻止使用处理器,直到该I/O操作完成的I/O操作。

注意: 同步I/O操作并不意味着同步I/O数据完整性完成或同步I/O文件完整性完成。

目前还不清楚“与[file descriptor]指示的文件相关的所有当前排队的I/O操作”是否适用于跨进程。 从概念上讲,我认为应该是这样的,但措辞并没有明确说明(或者说不是非常清晰)。它肯定适用于当前进程中引用相同文件的任何打开文件描述符。不清楚它是否适用于当前进程中以前打开(并关闭)的文件描述符。如果它适用于所有进程,则应该包括当前进程的排队I/O。如果它不适用于所有进程,则可能不包括。

鉴于此以及fsync()的基本原理注释,最安全的做法是假设fsync()操作对关闭的文件描述符关联的排队操作没有影响。如果您想让fsync()有效,请在关闭文件描述符之前调用它。


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