Win32:写入文件而不缓冲?

时间:2008-11-25 15:54:24

标签: winapi file-io buffering

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

额外信息:句柄将是子进程的继承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标志,由于同样的原因我无法使用 - 无法控制使用句柄的进程,因此我无法根据此标志的要求手动对齐写入。

编辑: 我专门制作了一个单独的项目,用于观察文件大小的变化。它使用.NET FileSystemWatcher类。我也写了更少的数据 - 总共大约100kB。

这是输出。查看时间戳中的秒数。

'builtin no-buffers'版本:

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.

5 个答案:

答案 0 :(得分:12)

在崩溃记录的情况下,我也被这种情况所困扰。

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

如果您想保证数据已写入设备,则需要FILE_FLAG_NO_BUFFERING,并且所有服务员都需要额外的工作。例如,这些写入必须对齐,因为缓冲区在返回之前一直向下移动到设备驱动程序。

知识库的差异为terse but informative article

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

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

答案 1 :(得分:5)

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

您可以在http://winntfs.com/2012/11/29/windows-write-caching-part-2-an-overview-for-application-developers/

找到关于此类内容的良好参考

干杯,

格雷格

答案 2 :(得分:2)

也许你对FlushFileBuffers

感到满意
  

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

     

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

他们警告说,调用 flush 来大量刷新缓冲区效率不高 - 而且最好只禁用缓存(即Tim answer):

  

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

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

答案 3 :(得分:2)

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

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

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

答案 4 :(得分:0)

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

Win API File Mapping