Win32:如何在不进行缓冲的情况下向文件写入数据?

14
我需要创建一个新的文件句柄,以便将对该句柄的任何写操作立即写入磁盘。
额外信息:该句柄将是子进程继承的STDOUT,因此我需要该进程的任何输出立即写入磁盘。
研究CreateFile文档,FILE_FLAG_WRITE_THROUGH标记看起来正是我需要的:
“写操作不会通过任何中间缓存,它们将直接传输到磁盘。”
我编写了一个非常基本的测试程序,嗯,它没有工作。我在CreateFile上使用了标志,然后在一个长循环中使用WriteFile(myHandle,...),在大约15秒钟内写入约100MB的数据(我添加了一些
然后,我设置了一个专业的监视环境,包括在资源管理器中不断地按下F5。结果:文件保持在0kB,然后在测试程序结束时跳至100MB左右。
接下来我尝试的是每次写入后手动刷新文件,使用FlushFileBuffers(myHandle)。这使观察到的文件大小按预期稳步增长。
那么,我的问题是,FILE_FLAG_WRITE_THROUGH不应该在不手动刷新文件的情况下完成这个操作吗?我错过了什么吗?
在“真实世界”的程序中,我无法刷新文件,因为我无法控制正在使用它的子进程。
还有FILE_FLAG_NO_BUFFERING标志,由于同样的原因无法使用-无法控制使用句柄的过程,因此无法像该标志所需的那样手动对齐写入。
编辑:我已经制作了一个专门用于观察文件大小如何更改的项目。它使用.NET FileSystemWatcher类。我也写了较少的数据-总计约100kB。
以下是输出。查看时间戳中的秒数。
“内置无缓冲版本:”
25.11.2008 7:03:22 PM: 10230 bytes added.
25.11.2008 7:03:31 PM: 10240 bytes added.
25.11.2008 7:03:31 PM: 10240 bytes added.
25.11.2008 7:03:31 PM: 10240 bytes added.
25.11.2008 7:03:31 PM: 10200 bytes added.
25.11.2008 7:03:42 PM: 10240 bytes added.
25.11.2008 7:03:42 PM: 10240 bytes added.
25.11.2008 7:03:42 PM: 10240 bytes added.
25.11.2008 7:03:42 PM: 10240 bytes added.
25.11.2008 7:03:42 PM: 10190 bytes added.

...以及“强制手动刷新”版本(每~2.5秒调用一次FlushFileBuffers()):

25.11.2008 7:06:10 PM: 10230 bytes added.
25.11.2008 7:06:12 PM: 10230 bytes added.
25.11.2008 7:06:15 PM: 10230 bytes added.
25.11.2008 7:06:17 PM: 10230 bytes added.
25.11.2008 7:06:19 PM: 10230 bytes added.
25.11.2008 7:06:21 PM: 10230 bytes added.
25.11.2008 7:06:23 PM: 10230 bytes added.
25.11.2008 7:06:25 PM: 10230 bytes added.
25.11.2008 7:06:27 PM: 10230 bytes added.
25.11.2008 7:06:29 PM: 10230 bytes added.

为什么您认为您需要这个? - Tim
2
+1 给专业的资源管理器 + F5。但我必须提醒您,同一操作系统内的可见性并不意味着刷新/耐久性。您必须更加专业:要么重置计算机,要么从系统中热拔出硬盘驱动器。我不确定重置或关闭电源是否会启动板载缓存刷新。只有在物理上移除驱动器或使用某些虚假存储时,您才可以确信刷新已经到达设备。 - Val
@RecognizeEvilasWaste 嗯...让我们看看它是否有效。 - Cristian Diaconescu
5个回答

13

在崩溃日志的背景下,我也曾被这个问题困扰。

FILE_FLAG_WRITE_THROUGH 仅保证您发送的数据在 WriteFile 返回之前已被发送到文件系统;它并不保证实际上已经发送到物理设备。因此,例如,如果您在带有此标志的句柄上执行 WriteFile 后执行 ReadFile,则保证该读取将返回您写入的字节,无论它是从文件系统缓存还是底层设备获取的。

如果您想要确保数据已写入设备,则需要使用FILE_FLAG_NO_BUFFERING,而伴随而来的是额外的工作。例如,这些写操作必须对齐,因为缓冲区要一路传输到设备驱动程序才会返回。

知识库中有一篇简要但信息丰富的文章介绍了两者之间的差异。

在您的情况下,如果父进程将比子进程更长寿,则可以:

  1. 使用 CreatePipe API 创建一个可继承的匿名管道。
  2. 使用 CreateFile 创建一个设置了 FILE_FLAG_NO_BUFFERING 的文件。
  3. 将可写句柄作为子进程的STDOUT提供给它。
  4. 在父进程中,从管道的可读句柄读取对齐的缓冲区,并将它们写入文件。

很好的想法,可以避免FILE_FLAG_NO_BUFFERING的限制!我只需要看看CreatePipe()如何使用缓冲区。谢谢! - Cristian Diaconescu
你确定这是真的吗?KB文章说:“数据被缓存(存储在磁盘缓存中);但是,它仍然直接写入文件。这种方法允许对该数据的读取操作从缓存数据(如果仍然存在)中满足读取请求,而不必进行文件读取以获取数据。写入调用在数据写入文件后才返回。”对我来说,这似乎意味着它将数据刷新到磁盘,但仍保留一个副本在缓存中以供未来读取。 - Laurenz Albe
请注意日期。我所写的内容在2008年是准确的,而且我怀疑那篇KB文章在那之后已经更新了。我的理解是,设备驱动程序的规则在此期间发生了变化(具体来说,自从Windows ~ 8以来,如果您正在使用不支持强制单元访问位的设备,则在提供FILE_FLAG_WRITE_THROUGH时,Windows会在写操作后插入一个刷新命令)。因此,FILE_FLAG_WRITE_THROUGH现在可以提供更严格的保证。但是,我已经不再在Windows平台上工作了,所以无法验证这是否正确。 - Tim Lesher

6

1
MSDN明确表示相反的情况。它说元数据正在被刷新。无论如何,令人惊讶的是FILE_FLAG_WRITE_THROUGH实际上应该用于什么。它基本上是一个普通的缓冲写入,只有一个区别,即脏页的回写立即开始(而不是“一些未指定的时间之后”)。这很好,但KB状态它总是同步运行并阻塞,直到完整的写入完成。这使得整个事情都没有意义(非缓冲可以异步运行没有问题)。 - Damon

2
也许你可以满足于FlushFileBuffers函数的效果:

刷新指定文件的缓冲区,并导致所有缓冲数据被写入文件。

通常,WriteFileWriteFileEx函数将数据写入操作系统定期写入磁盘或通信管道的内部缓冲区。 FlushFileBuffers 函数将指定文件的所有缓冲信息写入设备或管道。

他们确实警告说,频繁调用flush函数刷新缓冲区是低效的,最好只是禁用缓存(例如Tim的答案):

由于系统内部的磁盘缓存交互作用,当对磁盘驱动器设备进行多次单独写入时,每次写入后使用FlushFileBuffers函数可能效率低下。如果应用程序正在执行多个写入操作并且还需要确保关键数据被写入持久介质,则应用程序应该使用非缓冲I/O而不是频繁调用FlushFileBuffers。要为非缓冲I/O打开文件,请使用FILE_FLAG_NO_BUFFERINGFILE_FLAG_WRITE_THROUGH标志调用CreateFile函数。这可以防止文件内容被缓存,并在每次写入时将元数据刷新到磁盘上。有关更多信息,请参见CreateFile
如果不是高性能情况,并且您不会经常进行刷新,则FlushFileBuffers可能足够(而且更容易)。

我已经在问题中提到了使用FlushFileBuffers作为直接(不起作用)解决方案的解决方法,这就是我想要避免的。此外,对于标志“FILE_FLAG_WRITE_THROUGH”的引用加1。 - Cristian Diaconescu
抱歉,我的意思是将引用标志“FILE_FLAG_NO_BUFFERING”加1(尽管此前已经回答过)。 - Cristian Diaconescu

2
您在资源管理器中查看的文件大小可能与文件系统知道的文件大小不完全同步,因此这不是衡量文件大小的最佳方法。刷新文件缓冲区将导致文件系统更新资源管理器正在查看的信息;关闭并重新打开也可能会达到同样的效果。
除了其他人提到的磁盘缓存问题外,写入操作确实在做您所希望的事情。只是在目录中执行“dir”命令可能无法显示最新的信息。
建议认为写入操作只是将数据“写入文件系统”并不完全正确。它确实将数据写入文件系统缓存,但也将数据发送到磁盘。写入操作可能意味着随后的读取操作从缓存中满足,但并不意味着我们跳过了某个步骤而没有将数据写入磁盘。请仔细阅读本文摘要。这对于几乎所有人来说都是一个令人困惑的问题。

1
也许你想考虑内存映射该文件。一旦你写入内存映射区域,文件就会被更新。 Win API 文件映射

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