19

在 POSIX 文件系统中持久重命名文件的正确方法是什么?特别想知道目录上的fsyncs。(如果这取决于 OS/FS,我问的是 Linux 和 ext3/ext4)。

注意:StackOverflow 上还有其他关于持久重命名的问题,但是 AFAICT 他们没有解决对目录进行 fsync 的问题(这对我来说很重要——我什至没有修改文件数据)。

我目前有(在 Python 中):

dstdirfd = open(dstdirpath, O_DIRECTORY|O_RDONLY)
rename(srcdirpath + '/' + filename, dstdirpath + '/' + filename)
fsync(dstdirfd)

具体问题

  • 这是否也隐式地 fsync 源目录?或者我可能会在电源循环后最终在两个目录中显示文件(这意味着我必须检查硬链接计数并手动执行恢复),即不可能保证持久的原子移动操作?
  • 如果我 fsync 源目录而不是目标目录,那是否也会隐式 fsync 目标目录?
  • 是否有任何有用的相关测试/调试/学习工具(故障注入器、自省工具、模拟文件系统等)?

提前致谢。

4

4 回答 4

16

不幸的是,戴夫的回答是错误的。

并非所有 POSIX 系统甚至都可能具有持久存储。如果他们这样做了,它仍然“允许”在系统崩溃后被冲洗。对于那些系统,无操作 fsync() 是有意义的,并且在 POSIX 下明确允许这样的 fsync()。可以在旧目录、新目录、两者或任何其他位置恢复文件也是合法的。POSIX 不保证系统崩溃或文件系统恢复。

真正的问题应该是:

如何在通过 POSIX API 支持的系统上进行持久重命名?

您需要在源目录目标目录上都执行 fsync(),因为这些 fsync() 应该做的最低限度是保持源目录或目标目录的外观。

fsync(destdirfd) 是否也隐式地 fsync 源目录?

  • 一般的POSIX:不,没有任何暗示
  • ext3/4:我不确定源目录和目标目录的更改是否最终都出现在日志中的同一事务中。如果他们这样做,他们就会一起承诺。

或者我可能会在电源循环(“崩溃”)后在两个目录中显示文件,即不可能保证持久的原子移动操作?

  • 一般的 POSIX:没有保证,但你应该 fsync() 两个目录,这可能不是原子持久的
  • ext3/4:你最少需要多少 fsync() 取决于挂载选项。例如,如果使用“dirsync”挂载,则不需要这两个 fsync() 中的任何一个。最多你需要两个 fsync(),但我几乎可以肯定一个就足够了(那么原子耐用)。

如果我 fsync 源目录而不是目标目录,那是否也会隐式 fsync 目标目录?

  • POSIX:没有
  • ext3/4:我真的相信两者都会在同一个事务中结束,所以你 fsync() 中的哪一个并不重要
  • 较旧的内核 ext3:(如果它们不在同一个事务中)一些不太理想的实现在 fsync() 上进行了太多同步,我敢打赌它确实提交了之前的每个事务。是的,正常的实现会首先将其链接到目标,然后将其从源中删除。所以 fsync(srcdirfd) 也会触发目标的 fsync() 。
  • ext4/latest ext3:如果它们不在同一个事务中,您可能能够完全独立地同步它们(两者都这样做)

是否有任何有用的相关测试/调试/学习工具(故障注入器、自省工具、模拟文件系统等)?

对于真正的崩溃,不。顺便说一句,真正的崩溃超出了内核的观点。硬件可能会重新排序写入(并且无法写入所有内容),从而破坏文件系统。Ext4 对此做好了更好的准备,因为它默认启用写屏障(挂载选项)(ext3 没有),并且可以使用日志校验和检测损坏(也是挂载选项)。

并且为了学习:找出这两个变化是否在日志中以某种方式联系起来!:-P

于 2013-05-11T18:11:56.333 回答
14

POSIX 定义rename 函数必须是 atomic

因此,如果您重命名(A,B),在任何情况下您都不应该在两个目录或两个目录中都看到文件的状态。无论您使用 fsync() 做什么或系统是否崩溃,总会有一个。

但这并不能解决确保 rename() 操作持久的问题。 POSIX 回答了这个问题

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

因此,如果您 fsync() 一个目录,挂起的重命名操作必须在它返回时传输到磁盘。任何一个目录的 fsync() 都应该足够了,因为 rename() 操作的原子性需要两个目录的更改以原子方式同步。

最后,与另一个答案中提到的博客文章中的主张相反,这样做的理由解释如下:

fsync() 函数旨在强制从缓冲区缓存中物理写入数据,并确保在系统崩溃或其他故障后,直到 fsync() 调用时间的所有数据都记录在磁盘上。由于这里没有定义“缓冲缓存”、“系统崩溃”、“物理写入”和“非易失性存储”的概念,因此措辞必须更加抽象。

声称符合 POSIX 并认为完成 fsync() 并且不会在系统崩溃期间保留这些更改的正确行为(即不是错误或硬件故障)的系统将不得不故意歪曲其规范.

(更新了更多信息:Linux 特定与可移植行为)

于 2011-04-27T18:56:09.167 回答
-1

您的问题的答案在很大程度上取决于所使用的特定操作系统、所使用的文件系统的类型以及源和目标是否在同一设备上。

我将首先阅读您正在使用的平台上的 rename(2) 手册页。

于 2011-04-12T17:59:45.267 回答
-4

在我看来,您正在尝试完成文件系统的工作。如果您移动文件,则内核和文件系统负责原子操作和故障恢复,而不是您的代码。

无论如何,这篇文章似乎解决了您关于 fsync 的问题:http: //blogs.gnome.org/alexl/2009/03/16/ext4-vs-fsync-my-take/

于 2011-04-13T05:14:46.013 回答