【问题标题】:Win32: Write to file without buffering?Win32:写入文件而不缓冲?
【发布时间】:2008-11-25 15:54:24
【问题描述】:

我需要创建一个新的文件句柄,以便对该句柄的任何写操作立即写入磁盘。

额外信息:句柄将是子进程继承的 STDOUT,因此我需要将该进程的任何输出立即写入磁盘。

研究CreateFile 文档,FILE_FLAG_WRITE_THROUGH 标志看起来正是我所需要的:

写操作不会通过 任何中间缓存,他们都会去 直接写入磁盘。

我编写了一个非常基本的测试程序,但是它不起作用。 我在 CreateFile 上使用了标志,然后在一个长循环中使用了WriteFile(myHandle,...),在大约 15 秒内写入了大约 100MB 的数据。 (我添加了一些Sleep()'s)。

然后我设置了一个专业的监控环境,包括在资源管理器中连续按“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.

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

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.

【问题讨论】:

  • 你认为你为什么需要这个?
  • +1 表示professional Explorer + F5。但我必须提醒您,同一操作系统内的可见性并不意味着flushing/durability。您必须采取更专业的行动:重置 PC 或从系统中热提取 HDD。我不确定重置或关机不会启动板载缓存刷新。只有当您物理移除驱动器或使用一些假存储时,您才能确定刷新确实到达了设备。
  • @RecognizeEvilasWaste 嗯...让我们看看它是否有效

标签: winapi file-io buffering


【解决方案1】:

在崩溃日志的上下文中,我也被这个问题所困扰。

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

如果您想保证数据已写入设备,那么您需要FILE_FLAG_NO_BUFFERING,以及所有随之而来的额外工作。例如,这些写入必须对齐,因为缓冲区在返回之前一直向下到达设备驱动程序。

知识库有一个terse but informative article 说明区别。

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

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

【讨论】:

  • 关于如何避开 FILE_FLAG_NO_BUFFERING 限制的好主意!我只需要看看缓冲如何与 CreatePipe() 一起工作。谢谢!
  • 你确定这是真的吗?知识库文章说:“数据被缓存(存储在磁盘缓存中);但是,它仍然直接写入文件。此方法允许对该数据进行读取操作以满足来自缓存数据的读取请求(如果它仍然存在),而不是必须读取文件来获取数据。写入调用在数据写入文件之前不会返回。” 对我来说,这似乎意味着它会刷新数据到磁盘,但仍会在缓存中保留一份副本以供将来读取。
  • 记下日期。我写的在 2008 年是真实的,我怀疑 KB 文章从那时起已经更新。 :-) 我的理解是,设备驱动程序的规则同时发生了变化(特别是,Windows 因为 ~Windows 8 在您提供 FILE_FLAG_WRITE_THROUGH 时在写入后插入一个刷新命令,如果您使用的设备不支持强制单元访问少量)。所以 FILE_FLAG_WRITE_THROUGH 现在可以提供更严格的保证。不过,我不再在 Windows 平台上工作,所以我无法验证是否是这种情况。
【解决方案2】:

这是一个老问题,但我想我可以补充一点。实际上,我认为这里的每个人都是错误的。当您使用 write-through 和 unbuffered-io 写入流时,它会写入磁盘,但不会更新与文件系统关联的元数据(例如资源管理器向您显示的内容)。

你可以在这里找到一个很好的参考资料http://winntfs.com/2012/11/29/windows-write-caching-part-2-an-overview-for-application-developers/

干杯,

格雷格

【讨论】:

  • MSDN 明确表示完全相反。它说正在刷新元数据。无论如何,令人惊讶的是 FILE_FLAG_WRITE_THROUGH 实际上应该用于什么。它基本上是一个普通的缓冲写入,只有一个区别,脏页的写回立即开始(而不是“稍后某个未指定的时间”)。这很好,除了 KB 声明它始终同步运行并阻塞直到完成写入。这使得整个事情变得荒谬(无缓冲可以异步运行没问题)。
【解决方案3】:

也许你对FlushFileBuffers足够满意:

刷新指定文件的缓冲区并将所有缓冲数据写入文件。

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

他们确实警告说调用 flush 来大量刷新缓冲区效率低下 - 最好禁用缓存(即 Tim 的 answer):

由于系统内的磁盘缓存交互,当多个写入分别执行时,在每次写入磁盘驱动器设备后使用 FlushFileBuffers 函数可能效率低下。如果应用程序正在对磁盘执行多次写入,并且还需要确保将关键数据写入持久性媒体,则应用程序应该使用无缓冲 I/O,而不是频繁调用 FlushFileBuffers。要为无缓冲 I/O 打开文件,请使用 FILE_FLAG_NO_BUFFERINGFILE_FLAG_WRITE_THROUGH 标志调用 CreateFile 函数。这可以防止文件内容被缓存,并在每次写入时将元数据刷新到磁盘。如需更多信息,请参阅 CreateFile

如果不是高性能情况,并且您不会过于频繁地刷新,那么 FlushFileBuffers 可能就足够了(而且更容易)。

【讨论】:

  • +1 用于引用标志“FILE_FLAG_WRITE_THROUGH”。我已经在问题中提到使用 FlushFileBuffers - 作为直接(不工作)解决方案的解决方法,这就是我试图避免的。
  • 对不起,我的意思是 +1 用于引用标志 'FILE_FLAG_NO_BUFFERING'(尽管之前已经回答过)
【解决方案4】:

您在资源管理器中查看的大小可能与文件系统对文件的了解并不完全同步,因此这不是衡量它的最佳方法。碰巧 FlushFileBuffers 会导致文件系统更新资源管理器正在查看的信息;关闭它并重新打开可能最终也会做同样的事情。

除了其他人提到的磁盘缓存问题之外,直写正在做你希望它做的事情。只是在目录中做'dir'可能不会显示最新信息。

建议直写仅将其写入“文件系统”的答案并不完全正确。它确实将其写入文件系统缓存,但也将数据向下发送到磁盘。直写可能意味着从缓存中满足后续读取,但这并不意味着我们跳过了一个步骤并且没有将其写入磁盘。仔细阅读article's summary。这对几乎每个人来说都是一个令人困惑的地方。

【讨论】:

    【解决方案5】:

    也许您想考虑对该文件进行内存映射。一旦您写入内存映射区域,文件就会更新。

    Win API File Mapping

    【讨论】:

      猜你喜欢
      • 2020-08-16
      • 2014-01-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-29
      • 2016-02-13
      相关资源
      最近更新 更多