刷新缓冲区、同步文件和内存层的区别:fflush、fsync和sync

18
我知道已经有类似的问题了,我看了一下,但是没有找到一个明确的、唯一的答案来回答我的问题。我只是在网上调查这些函数及其与内存层的关系。特别是我发现了这篇精美的文章,它给了我一个很好的关于内存层的见解。

memory layers

看起来 fflush() 将数据从应用程序移动到内核文件系统缓冲区,这是没问题的,每个人似乎都同意这一点。唯一让我困惑的是,在同一篇文章中,他们假设了一个写回缓存,称使用 fsync() "将数据保存到稳定存储层",然后补充说 "存储本身可能会将数据存储在写回缓存中,因此对于使用 O_DIRECT 打开的文件仍需要使用 fsync() 以将数据保存到稳定存储器中"。

阅读此处那里,似乎真相是fsync()sync()可以让数据进入存储设备,但如果该设备具有缓存层,则仅在此处移动,而不是立即移动到永久存储设备中,如果出现电力故障,则可能会丢失数据。除非我们启用了带有屏障的文件系统,然后“sync()/fsync()和其他一些操作将导致适当的CACHE FLUSH(ATA)或SYNCHRONIZE CACHE(SCSI)命令发送到设备”[来自您网站上的答案]
问题:
  1. 如果要更新的数据已经在内核缓冲区中,并且我的设备具有以写回方式为主的易失性缓存层,是否像文章所说,诸如fsync()(和我想象的sync())之类的操作会将数据同步到跳过易失性缓存的稳定内存层?我认为这只适用于写穿透缓存,而不是写回缓存。从我所读的内容来看,我理解的是,在写回缓存上,对于 fsync(),它只能将数据发送到设备,然后设备将其放入易失性缓存中,只有在此之后,它们才会进入永久内存。
  2. 我读到fsync()使用文件描述符,然后使用单个文件,而sync()会导致缓冲区总体部署,因此适用于每个要更新的数据。也从这page得知,fsync()等待写入磁盘结束,而sync()则不等待实际写入磁盘的结束。这两者之间的内存数据传输是否还有其他差异?

感谢那些尝试帮助的人


2
这个PostgreSQL vs. fsync()视频包含了很多关于fsync()的信息。它可能有助于解决难题的一部分:https://fosdem.org/2019/schedule/event/postgresql_fsync/ - Jorge Issa
4个回答

4

1. 正如你从研究中得出的结论一样,fflush 将用户空间缓冲区的数据与内核级别的缓存同步(由于它使用在用户级别上存在但对内核不可见的 FILE 对象),而 fsyncsync(直接与文件描述符一起工作)将内核缓存的数据与设备同步。然而,后者并不保证数据已经实际写入存储设备——因为这些设备通常也带有自己的缓存。我认为,使用 MS_SYNC 标志调用的 msync 也可能存在相同的情况。

相关地,当谈论该主题时,我发现区分 同步同步式 操作非常有用。以下是 Robert Love 简明扼要的解释:

同步写操作不会返回,直到被写入的数据至少被存储在内核的缓冲区中。[...] 同步操作比仅仅同步操作更加严格和安全。同步写操作将数据刷新到磁盘,确保磁盘上的数据始终与相应的内核缓冲区同步。

有了这个理解,你可以使用 O_SYNC 标志调用 open(连同某些具有写权限的其他标志)来强制进行同步写操作。同样,正如你正确地假设的那样,这只能在启用了 WRITE THROUGH 磁盘缓存策略时工作,这实际上等于 禁用 磁盘缓存。

你可以阅读关于如何在 Linux 上禁用磁盘缓存的 这个回答。确保还要查看 这个网站,它除了 ATA 设备外,还涵盖了基于 SCSI 的设备(有关不同类型的磁盘的信息,请参见 Microsoft SQL Server 2005 的 此页面,最后更新时间为:2018 年 4 月 19 日)。

说到这里,阅读一下关于如何在 Windows 机器 上处理该问题非常有启发性:

要为无缓冲 I/O 打开文件,请使用 FILE_FLAG_NO_BUFFERING 和 FILE_FLAG_WRITE_THROUGH 标志调用 CreateFile 函数。这将防止文件内容被缓存,并在每次写入时刷新元数据到磁盘。有关详细信息,请参见 CreateFile。

显然,这是如何确保数据完整性的Microsoft SQL Server 2005家族:

所有版本的SQL Server都使用Win32 CreateFile函数打开日志和数据文件。当被SQL Server打开时,dwFlagsAndAttributes成员包括FILE_FLAG_WRITE_THROUGH选项。[...]此选项指示系统通过任何中间缓存直接写入磁盘。系统仍然可以缓存写操作,但不能懒惰地刷新它们。

我特别提到这一点是因为2012年博客文章显示某些SATA磁盘忽略FILE_FLAG_WRITE_THROUGH!我不知道现在的情况如何,但似乎要确保写入磁盘真正同步,您需要:

  1. 使用设备驱动程序禁用磁盘缓存。
  2. 确保您正在使用的特定设备支持write-through / no-caching策略。

但是,如果您正在寻找数据完整性的保证,则可以购买具有超越电容器(通常仅足以完成正在进行的写入过程)的电池供电的磁盘。正如上面提到的博客文章中的结论:

底线,为您的数据和事务日志文件使用Enterprise-Class磁盘。[...]实际上,情况并不像看起来那么严重。许多RAID控制器具有带电池支持的缓存,并且不需要遵守写入要求。

2.为了部分回答第二个问题,这是来自man页面的内容,SYNC(2)

根据标准规范(例如,POSIX.1-2001),sync()调度写入,但可能在实际写入完成之前返回。但是,自版本1.3.20 Linux确实在等待。(这仍然不能保证数据完整性:现代磁盘具有大缓存。)

这将意味着fsyncsync工作方式不同,但请注意它们都在unistd.h中实现,这表明它们之间存在一些一致性。但是,我会遵循Robert Love的建议,在编写自己的代码时不建议使用sync系统调用。

sync()的唯一真正用途是在sync实用程序的实现中。应用程序应使用fsync()和fdatasync()来提交所需文件描述符的数据到磁盘。请注意,在繁忙的系统上,sync()可能需要几分钟或更长时间才能完成。


0

我没有任何解决方案,但肯定钦佩这个问题。

从您提供的参考资料中,我了解到并没有标准。标准在内核的某个地方结束。内核控制设备驱动程序,设备驱动程序(可能由磁盘制造商提供)通过 API 控制磁盘(设备上有小型计算机)。制造商可能已经添加了电容器/电池,以足够的电力在断电时刷新其设备缓冲区,也可能没有。设备可能提供同步功能,但是否真正同步(刷新)设备缓冲区是未知的(取决于设备)。因此,除非您根据规格选择和安装设备(并验证这些规格),否则您永远不确定。


0

是的,fflush()确保数据离开进程内存空间,但它可能在等待写回的脏页面中。这可以防止应用程序异常终止,但无法防止系统崩溃或电源故障。即使电源有备份,由于某些软件漏洞,系统也可能崩溃!正如其他答案/评论中所提到的,从脏页中获取数据通过磁盘磁性或任何SSD将其写入磁盘控制器或驱动程序的易失性缓冲区的组合,需要正确的调用或打开选项以及正确的控制器和设备!调用可以使您更好地控制开销,在事务结束时一次性写入更多内容。

例如,关系数据库管理系统(RDBMS)不仅需要担心持有文件的数据库,还需要更多地担心允许恢复的日志文件,包括磁盘丢失后以及在崩溃后的任何RDBMS重启。实际上,一些日志文件可能比数据库更同步,以保持速度,因为恢复不是频繁的过程,通常也不是长时间的过程。 事务写入日志的内容如果日志完好无损,就可以保证可恢复。


0

这是一个公平的问题。即使处理了错误条件,您的存储中仍然存在数据存在的风险。

fsync 的 man 页面清楚地解释了这个问题! :) 对于需要更严格保证其数据完整性的应用程序,Mac OS X 提供了 F_FULLFSYNC fcntl。F_FULLFSYNC fcntl 要求驱动器将所有缓冲数据刷新到永久存储。

需要严格写入顺序的应用程序(如数据库)应使用 F_FULLFSYNC 以确保它们的数据按照预期顺序写入。有关详细信息,请参见 fcntl(2)。


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