12

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

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

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

写操作不会经过任何中间缓存,它们会直接进入磁盘。

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

然后我建立了一个专业的监控环境,包括在资源管理器中连续点击“F5”。结果:文件保持在 0kB,然后在测试程序结束时跳转到 100MB。

接下来我尝试的是在每次写入后手动刷新文件,使用FlushFileBuffers(myHandle). 正如预期的那样,这使得观察到的文件大小变得良好且稳定。

那么,我的问题是,不应该在手动刷新文件的情况下FILE_FLAG_WRITE_THROUGH做到这一点吗?我错过了什么吗?在“真实世界”程序中,我无法刷新文件,因为我无法控制正在使用它的子进程。

还有一个FILE_FLAG_NO_BUFFERING标志,出于同样的原因,我不能使用它 - 无法控制使用句柄的进程,因此我无法按照该标志的要求手动对齐写入。

编辑:我做了一个单独的项目,专门用于观察文件大小的变化。它使用 .NETFileSystemWatcher类。我也写了更少的数据——总共大约 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.
4

5 回答 5

12

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

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

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

知识库有一篇关于差异的简短但内容丰富的文章。

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

  1. 使用CreatePipeAPI 创建可继承的匿名管道。
  2. 用于CreateFile创建带有FILE_FLAG_NO_BUFFERINGset 的文件。
  3. 将管道的可写句柄作为其 STDOUT 提供给子级。
  4. 在父进程中,从管道的可读句柄中读取对齐的缓冲区,并将它们写入文件。
于 2008-11-25T17:27:52.730 回答
5

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

你可以在这里找到关于这类东西的很好的参考http://winntfs.com/2012/11/29/windows-write-caching-part-2-an-overview-for-application-developers/

干杯,

格雷格

于 2013-07-24T15:38:29.340 回答
2

也许您可以对以下内容感到满意FlushFileBuffers

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

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

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

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

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

于 2010-03-03T20:09:13.907 回答
2

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

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

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

于 2010-08-27T20:28:34.407 回答
0

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

Win API 文件映射

于 2018-02-15T20:40:11.867 回答