我以前认为文件更改是直接保存到磁盘中的,即只要我关闭文件并决定单击/选择保存。然而,在最近的一次谈话中,我的一个朋友告诉我这通常不是真的。操作系统(特别是我们谈论的 Linux 系统)将更改保存在内存中,并且它有一个守护进程,它实际上将内容从内存写入磁盘。
他甚至举了外部闪存驱动器的例子:它们被安装到系统中(复制到内存中),有时会发生数据丢失,因为守护程序尚未将内容保存到闪存中;这就是我们卸载闪存驱动器的原因。
我对操作系统的功能一无所知,所以我完全不知道这是否属实以及在什么情况下。我的主要问题是:这是否像 Linux/Unix 系统(可能还有其他操作系统)中描述的那样发生?例如,这是否意味着如果我在编辑和保存文件后立即关闭计算机,我的更改很可能会丢失?也许这取决于磁盘类型——传统硬盘驱动器与固态磁盘?
该问题专门针对具有存储信息的磁盘的文件系统,即使任何澄清或比较都受到好评。
他们可能是。我不会说“最有可能”,但可能性取决于很多事情。
提高文件写入性能的一种简单方法是让操作系统只缓存数据,告诉(欺骗)应用程序写入经过,然后稍后实际执行写入。如果同时有其他磁盘活动,这将特别有用:操作系统可以优先读取并稍后执行写入。它还可以完全消除对实际写入的需要,例如,在之后快速删除临时文件的情况下。
如果存储速度较慢,缓存问题会更加明显。将文件从快速 SSD 复制到慢速 USB 记忆棒可能会涉及大量写入缓存,因为 USB 记忆棒无法跟上。但是您的
cp
命令返回得更快,因此您可以继续工作,甚至可能编辑刚刚复制的文件。当然,这样的缓存有你注意到的缺点,一些数据在实际保存之前可能会丢失。如果他们的编辑告诉他们写入成功,但文件实际上不在磁盘上,用户会很生气。这就是为什么有
fsync()
系统调用的原因,它应该只在文件实际到达磁盘后才返回。在向用户报告写入成功之前,您的编辑器可以使用它来确保数据正常。我说“应该”,因为驱动器本身可能会对操作系统说同样的谎言并说写入已完成,而文件实际上只存在于驱动器内的易失性写入缓存中。根据驱动器,可能没有办法解决这个问题。
除了
fsync()
,还有sync()
和syncfs()
系统调用要求系统确保所有系统范围的写入或特定文件系统上的所有写入都已命中磁盘。该实用程序sync
可用于调用这些。然后还有
O_DIRECT
to 标志open()
,它应该是“尽量减少进出该文件的 I/O 的缓存影响”。删除缓存会降低性能,因此主要由执行自己的缓存并希望控制它的应用程序(数据库)使用。(O_DIRECT
并非没有问题,手册页中关于它的评论有些有趣。)断电时会发生什么也取决于文件系统。您应该关注的不仅仅是文件数据,还有文件系统元数据。如果找不到,将文件数据保存在磁盘上并没有多大用处。仅将文件扩展为更大的大小将需要分配新的数据块,并且需要在某处进行标记。
文件系统如何处理元数据更改以及元数据和数据写入之间的顺序变化很大。例如,
ext4
如果你设置了 mount 标志data=journal
,那么所有的写入——甚至是数据写入——都会通过日志并且应该是相当安全的。这也意味着它们被写入两次,因此性能下降。默认选项尝试对写入进行排序,以便在更新元数据之前数据位于磁盘上。其他选项或其他文件系统可能更好或更差;我什至不会尝试全面的研究。实际上,在负载较轻的系统上,文件应该在几秒钟内到达磁盘。如果您正在处理可移动存储,请在拉取媒体之前卸载文件系统,以确保数据实际发送到驱动器,并且没有进一步的活动。(或者让您的 GUI 环境为您执行此操作。)
有一种非常简单的方法可以证明文件编辑总是直接保存到磁盘是不正确的,即存在首先不受磁盘支持的文件系统这一事实。如果文件系统一开始就没有磁盘,那么它永远不可能将更改写入磁盘。
一些例子是:
tmpfs
,仅存在于 RAM 中(或更准确地说,存在于缓冲区缓存中)的文件系统ramfs
, 只存在于 RAM 中的文件系统sysfs
,procfs
,devfs
,shmfs
, ...)但即使对于磁盘支持的文件系统,这通常也不是真的。How To Corrupt An SQLite Database页面有一个名为Failure to sync的章节,它描述了写入(在这种情况下是提交到 SQLite 数据库)可能无法到达磁盘的许多不同方式。SQLite 也有一份白皮书,解释了您必须跳过许多障碍才能保证SQLite 中的原子提交。(请注意,Atomic Write比Write更难,但写入磁盘当然是 atomic writing 的子问题,你也可以从这篇论文中学到很多关于这个问题的知识。)这篇论文有一个可能出错的部分,其中包括一个关于不完整的磁盘刷新给出了一些可能会阻止写入到达磁盘的微妙复杂性的示例(例如,HDD 控制器报告它已写入磁盘,但实际上它没有 - 是的,有 HDD 制造商这样做,根据 ATA 规范,它甚至可能是合法的,因为它在这方面的措辞含糊不清)。
确实,包括 Unix、Linux 和 Windows 在内的大多数操作系统都使用写缓存来加速操作。这意味着在不关闭计算机的情况下关闭计算机是一个坏主意,并且可能导致数据丢失。如果您在准备好移除 USB 存储设备之前移除它,情况也是如此。
大多数系统还提供使写入同步的选项。这意味着数据将在应用程序收到成功确认之前存储在磁盘上,但代价是速度较慢。
简而言之,您应该正确关闭计算机并正确准备要移除的 USB 存储设备是有原因的。
1. 基于闪存的存储
当您有选择时,您不应该让基于闪存的存储在没有完全关机的情况下断电。
在像 SD 卡这样的低成本存储上,您可能会丢失整个擦除块(比 4KB 大几倍),丢失可能属于不同文件或文件系统基本结构的数据。
一些昂贵的 SSD 可能声称在遇到电源故障时能提供更好的保证。然而,第三方测试表明,许多昂贵的 SSD 无法做到这一点。为“磨损均衡”重新映射块的层是复杂且专有的。可能的故障包括驱动器上的所有数据丢失。
2017:https ://dl.acm.org/citation.cfm?id=2992782&preflayout=flat
2013:https ://www.usenix.org/system/files/conference/fast13/fast13-final80.pdf?wptouch_preview_theme=enabled
2.旋转硬盘驱动器
旋转硬盘具有不同的特性。为了安全和简单起见,我建议假设它们与基于闪存的存储具有相同的实际不确定性。
除非你有具体的证据,而你显然没有。我没有旋转硬盘的比较数据。
HDD 可能会在一个未完全写入的扇区中留下一个错误的校验和,这将在以后给我们带来很好的读取失败。从广义上讲,HDD的这种故障模式是完全可以预料的;本机 Linux 文件系统在设计时就考虑到了这一点。
fsync()
他们的目标是在面对这种类型的断电故障时保持合同。(我们真的很想在 SSD 上看到这一点)。但是我不确定 Linux 文件系统是否在所有情况下都能实现这一点,或者这是否可能。
此类故障后的下一次引导可能需要文件系统修复。这是Linux,文件系统修复可能会问一些你不明白的问题,你只能按Y,希望它会自行解决。
2.1 如果你不知道 fsync() 合约是什么
fsync() 合约是好消息和坏消息的来源。你必须先了解好消息。
好消息:
fsync()
有据可查的是写入文件数据的正确方法,例如当您点击“保存”时。众所周知,例如文本编辑器必须使用 . 原子替换现有文件rename()
。这是为了确保您始终要么保留旧文件,要么获取新文件(fsync()
在重命名之前编辑)。您不想留下新文件的半写版本。坏消息:多年来,在最流行的 Linux 文件系统上调用 fsync() 可以有效地让整个系统挂起数十秒。由于应用程序对此无能为力,因此乐观地使用不带 fsync() 的 rename() 是很常见的,这在这个文件系统上似乎相对可靠。
因此,存在不正确使用 fsync() 的应用程序。
该文件系统的下一个版本通常避免了 fsync() 挂起 - 同时它开始依赖于 fsync() 的正确使用。
这一切都很糟糕。许多相互冲突的内核开发人员使用的轻蔑和谩骂可能无助于理解这段历史。
当前的解决方案是当前最流行的 Linux 文件系统
默认支持 rename() 模式而不需要 fsync()与以前的版本实现“bug-for-bug 兼容性”。这可以通过 mount 选项禁用noauto_da_alloc
。这不是一个完整的保护。基本上它会在 rename() 时间刷新挂起的 IO,但它不会在重命名之前等待 IO 完成。这比例如 60 秒的危险窗口要好得多!另请参阅哪些文件系统在使用 rename() 替换现有文件时需要 fsync() 以确保崩溃安全?
一些不太流行的文件系统不提供保护。XFS拒绝这样做。而且UBIFS也没有实现它,显然它可以被接受,但需要做很多工作才能使它成为可能。同一页面指出,UBIFS 在数据完整性方面还有其他几个“待办事项”问题,包括断电。UBIFS 是一种直接用于闪存的文件系统。我想 UBIFS 提到的闪存存储的一些困难可能与 SSD 错误有关。
在负载较轻的系统上,内核将让新写入的文件数据在 a 之后在页面缓存中放置大约 30 秒
write()
,然后再将其刷新到磁盘,以针对很快再次删除或修改它的情况进行优化。Linux 的
dirty_expire_centisecs
默认值为 3000(30 秒),并控制新写入的数据“过期”之前的时间。(见https://lwn.net/Articles/322823/)。请参阅https://www.kernel.org/doc/Documentation/sysctl/vm.txt了解更多相关的可调参数,以及 google 了解更多信息。(例如谷歌上
dirty_writeback_centisecs
)。Linux 默认为
/proc/sys/vm/dirty_writeback_centisecs
500(5 秒),PowerTop 建议将其设置为 1500(15 秒)以降低功耗。延迟回写还让内核有时间在开始将文件写入磁盘之前查看文件的大小。具有延迟分配的文件系统(例如 XFS,现在可能还有其他文件系统)在必要时甚至不会选择磁盘上的哪个位置来放置新写入的文件数据,这与为 inode 本身分配空间是分开的。例如,这通过让他们避免将大文件的开头放在其他文件之间的 1 兆间隙中来减少碎片。
如果正在写入大量数据,则可以通过页面缓存中可以有多少脏(尚未同步到磁盘)数据的阈值来触发回写到磁盘。
但是,如果您没有做太多其他事情,您的硬盘驱动器活动指示灯在点击保存小文件后的 5(或 15)秒内不会亮起。
如果您的编辑器
fsync()
在写入文件后使用,内核将立即将其写入磁盘。(并且fsync
在数据实际发送到磁盘之前不会返回)。磁盘中的写入缓存也可以是一件事,但磁盘通常会尝试尽快将其写入缓存提交到永久存储,这与 Linux 的页面缓存算法不同。磁盘写入缓存更像是一个存储缓冲区,用于吸收少量的写入突发,但也可能延迟写入以支持读取,并为磁盘固件提供优化查找模式的空间(例如,执行两次附近的写入或读取而不是执行一次,然后寻找远方,然后寻找回来。)
在旋转(磁性)磁盘上,如果在写入之前有挂起的读取/写入,您可能会看到几个 7 到 10 毫秒的寻道延迟,然后来自 SATA 写入命令的数据实际上不会断电。(关于这个问题的其他一些答案更详细地介绍了磁盘写入缓存和日志型 FS 可以用来避免损坏的写入屏障。)