ext4/fsync 在 Android (Java) 中的情况不明朗

2022-09-04 22:01:56

Tim Bray的文章“安全地保存数据”给我留下了悬而未决的问题。今天,它已经一个多月了,我还没有看到任何后续工作,所以我决定在这里讨论这个话题。

本文的一点是,在使用 FileOutputStream 时,应调用 FileDescriptor.sync() 以确保安全。起初,我非常生气,因为在我做Java的12年里,我从未见过任何Java代码进行同步。特别是因为处理文件是一件非常基本的事情。此外,FileOutputStream的标准JavaDoc从未暗示过同步(Java 1.0 - 6)。经过一些研究,我认为 ext4 实际上可能是第一个需要同步的主流文件系统。(是否还有其他文件系统建议进行显式同步?

我很欣赏关于这个问题的一些一般想法,但我也有一些具体的问题:

  1. Android 何时会同步到文件系统?这可能是周期性的,并且还基于生命周期事件(例如,应用程序的进程进入后台)。
  2. FileDescriptor.sync() 负责同步元数据吗?这是同步已更改文件的目录。与 FileChannel.force() 进行比较。
  3. 通常,不会直接写入 FileOutputStream。这是我的解决方案(你同意吗?):
    FileOutputStream fileOut = ctx.openFileOutput(file, Context.MODE_PRIVATE);
    BufferedOutputStream out = new BufferedOutputStream(fileOut);
    try {
        out.write(something);
        out.flush();
        fileOut.getFD().sync();
    } finally {
        out.close();
    }
    

答案 1

Android将在需要时进行同步 - 例如当屏幕关闭时,关闭设备等。如果您只是在查看“正常”操作,则永远不需要应用程序进行显式同步。

当用户将电池从其设备中取出(或对内核进行硬重置)并且您希望确保不会丢失任何数据时,问题就来了。

因此,首先要意识到:问题是当突然断电时,因此不会发生干净关闭,以及此时持久性存储中将会发生什么的问题。

如果您只是编写一个独立的新文件,那么您做什么并不重要。用户可以在您开始写作之前,在写作过程中拉动电池,等等。如果您不同步,这只是意味着从您完成写入开始有更长的时间,在此期间拉动电池将丢失数据。

这里最大的问题是当您想要更新文件时。在这种情况下,当您下次读取文件时,您希望具有以前的内容内容。您不希望半途而废,也不想丢失数据。

这通常是通过将数据写入新文件,然后从旧文件切换到该文件来完成的。在 ext4 之前,您知道,一旦您完成写入文件,其他文件的进一步操作将不会在磁盘上进行,直到该文件上的操作为止,因此您可以安全地删除以前的文件或以其他方式执行依赖于新文件完全写入的操作。

但是现在,如果您写入新文件,然后删除旧文件,并且电池被拉出,当您下次启动时,您可能会看到旧文件被删除并创建了新文件,但新文件的内容不完整。通过执行同步,可以确保此时已完全写入新文件,因此可以执行取决于该状态的进一步更改(如删除旧文件)。


答案 2

fileOut.getFD().sync();应该在 finally 子句上,在 .close()

sync()比考虑耐用性更重要。close()

因此,每次您想“完成”文件的工作时,您都应该在处理之前完成它。sync()close()

posix 不保证当您发出 .close()


推荐