Android (Java) 中的 ext4/fsync 情况不清楚
Tim Bray 的文章“安全保存数据”给我留下了开放式问题。今天,它已经过去一个多月了,我还没有看到任何后续进展,所以我决定在这里讨论这个话题。
本文的一点是,在使用 FileOutputStream 时,应该调用 FileDescriptor.sync() 以确保安全。起初,我非常恼火,因为我从事 Java 工作的 12 年里从未见过任何 Java 代码进行同步。特别是因为处理文件是一件非常基本的事情。此外,FileOutputStream 的标准 JavaDoc 从未暗示过同步(Java 1.0 - 6)。经过一番研究,我认为 ext4 实际上可能是第一个需要同步的主流文件系统。 (是否有其他文件系统建议显式同步?)
我很欣赏对此事的一些一般想法,但我也有一些具体问题:
- Android 何时会同步到文件系统?这可以是周期性的并且另外基于生命周期事件(例如应用程序的进程进入后台)。
- FileDescriptor.sync() 是否负责同步元数据?那就是同步更改文件的目录。与 FileChannel.force() 进行比较。
- 通常,人们不会直接写入FileOutputStream。这是我的解决方案(你同意吗?):
FileOutputStream fileOut = ctx.openFileOutput(file, Context.MODE_PRIVATE); BufferedOutputStream out = new BufferedOutputStream(fileOut); 尝试 { 写出(某事); 出.flush(); fileOut.getFD().sync(); } 最后 { 关闭(); }
Tim Bray's article "Saving Data Safely" left me with open questions. Today, it's over a month old and I haven't seen any follow-up on it, so I decided to address the topic here.
One point of the article is that FileDescriptor.sync() should be called to be on the safe side when using FileOutputStream. At first, I was very irritated, because I never have seen any Java code doing a sync during the 12 years I do Java. Especially since coping with files is a pretty basic thing. Also, the standard JavaDoc of FileOutputStream never hinted at syncing (Java 1.0 - 6). After some research, I figured ext4 may actually be the first mainstream file system requiring syncing. (Are there other file systems where explicit syncing is advised?)
I appreciate some general thoughts on the matter, but I also have some specific questions:
- When will Android do the sync to the file system? This could be periodic and additionally based on life cycle events (e.g. an app's process goes to the background).
- Does FileDescriptor.sync() take care of syncing the meta data? That is syncing the directory of the changed file. Compare to FileChannel.force().
- Usually, one does not directly write into the FileOutputStream. Here's my solution (do you agree?):
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(); }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
Android 会在需要时进行同步,例如当屏幕关闭、关闭设备等时。如果您只是考虑“正常”操作,则永远不需要应用程序进行显式同步。
当用户从设备中取出电池(或对内核进行硬重置)时,问题就会出现,并且您希望确保不会丢失任何数据。
因此,首先要认识到:问题是当电源突然断电时,因此无法完全关闭,以及此时持久存储中会发生什么的问题。
如果您只是编写一个独立的新文件,那么您做什么并不重要。用户可能在您正在书写时、在开始书写之前等时拔出了电池。如果您不同步,则仅意味着从您完成书写后还有一段较长的时间来拔出电池将会丢失数据。
这里最大的问题是当您想要更新文件时。在这种情况下,当您下次读取该文件时,您想要或者之前内容,或者新内容内容。您不想写到一半,或者丢失数据。
这通常是通过将数据写入新文件,然后从旧文件切换到该文件来完成的。在 ext4 之前,您知道,一旦完成写入文件,对其他文件的进一步操作将不会在磁盘上进行,直到该文件上的操作为止,因此您可以安全地删除以前的文件或以其他方式执行依赖于新文件的操作正在被完全写入。
但是现在,如果您写入新文件,然后删除旧文件,然后拔掉电池,那么当您下次启动时,您可能会看到旧文件被删除并创建了新文件,但新文件的内容不完整。通过执行同步,您可以确保新文件在此时已完全写入,以便可以根据该状态进行进一步的更改(例如删除旧文件)。
Android will do the sync when it needs to -- such as when the screen turns off, shutting down the device, etc. If you are just looking at "normal" operation, explicit sync by applications is never needed.
The problem comes when the user pulls the battery out of their device (or does a hard reset of the kernel), and you want to ensure you don't lose any data.
So the first thing to realize: the issue is when power is suddenly lost, so a clean shutdown can not happen, and the question of what is going to happen in persistent storage at that point.
If you are just writing a single independent new file, it doesn't really matter what you do. The user could have pulled the battery while you were in the middle of writing, right before you started writing, etc. If you don't sync, it just means there is some longer time from when you are done writing during which pulling the battery will lose the data.
The big concern here is when you want to update a file. In that case, when you next read the file you want to have either the previous contents, or the new contents. You don't want to get something half-way written, or lose the data.
This is often done by writing the data in a new file, and then switching to that from the old file. Prior to ext4 you knew that, once you had finished writing a file, further operations on other files would not go on disk until the ones on that file, so you could safely delete the previous file or otherwise do operations that depend on your new file being fully written.
However now if you write the new file, then delete the old one, and the battery is pulled, when you next boot you may see that the old file is deleted and new file created but the contents of the new file is not complete. By doing the sync, you ensure that the new file is completely written at that point so can do further changes (such as deleting the old file) that depend on that state.
fileOut.getFD().sync();
应位于finally 子句中,close()
之前。考虑到持久性,
sync()
比close()
更重要。因此,每次您想要“完成”文件处理时,都应该在
close()
之前对其进行sync()
处理。posix 不保证当您发出
close()
时挂起的写入将被写入磁盘。fileOut.getFD().sync();
should be on the finally clause, before theclose()
.sync()
is way more important thanclose()
considering durability.So, everytime you want to 'finish' working on a file you should
sync()
it beforeclose()
ing it.posix does not guarantee that pending writes will be written to disk when you issue a
close()
.