深入了解Vista SP1文件复制改进

这是一篇翻译文章,原文作者:Mark Russinovich,点击查看原文

Windows Vista SP1在初始版本的Vista基础上包含了大量的改进,这些改进涉及应用程序兼容性、设备支持、电源管理、安全以及可靠性。我们可以在Notable Changes in Windows Vista Service Pack 1白皮书中看到有关这些改进的详细列表,白皮书可以在这里下载到。这篇文档中最重要的改进之一就是在多种复制操作下的性能增强,包括同一硬盘上的本地复制、从远程的非Windows Vista系统上复制、以及在SP1系统之间复制等。这些增强是如何实现的?答案很复杂,并且主要取决于Windows XP和Vista之中的文件复制引擎的改进。每个人都需要复制文件,因此我觉得我们应该在除了讨论”…的情况下”之外,还应该深入了解一下SP1中的复制引擎是如何增强性能的。

复制文件看起来是个相当简单的过程:打开源文件,创建目标文件,然后从源文件读取,并写入到目标文件。然而实际上,复制文件的性能是通过准确的进度条大小、CPU使用、内存使用以及吞吐量等情况判断的。一般来说,从一方面进行优化就有可能影响到另一方面。另外,复制引擎缺少有助于实现更好传输率的描述信息。例如,如果复制引擎知道在复制操作的过程中我们不打算访问目标数据,那么引擎就会避免将文件的数据缓存到内存中;然而,如果引擎知道文件需要被其他应用程序持续访问,或者对于文件服务器,客户端系统需要共享这些文件,引擎就会将目标系统中的被复制文件的数据缓存起来。

老版本Windows中的文件复制

为了权衡速率以及不完善的描述信息,Windows复制引擎会尝试尽可能将不同的场景做到完美。在Windows Vista之前,复制引擎会直接将源文件和目标文件都用缓存的模式打开,并持续将源文件以64KB(通过网络复制时以64KB为单位,这是因为SMB 1.0协议对于单个文件的大小存在限制)为单位读取,并以同样的单位将数据写入目标位置。当文件被缓存I/O访问时,情况和内存映射I/O或具有非缓冲标记(no-buffering flag)I/O的情况完全相反,至少在内存管理器判断出内存应该被其他用户使用,包括用于扩缓存其他文件的数据之前,数据的读取和写入都是在内存中进行的。

复制引擎通过Windows缓存管理器执行并发的读取操作,这在本质上实际就是当Explorer正在忙于将数据写入不同的硬盘或远程系统的时候,在后台复制源文件。同时复制引擎还要通过缓存管理器的后台写入机制将被复制文件的内容从内存及时重新写回硬盘,这样如果有必要,内存才可以被尽快重新使用。而且在这样的情况下,如果系统或磁盘出错,才有可能将数据的损失降到最低。为了看到这一机制,我们可以使用Process Monitor追踪一个256KB的文件在Windows XP中从一个目录复制到另一个目录时的数据读取和写入过程:

Explorer的第一个读取操作是Event 0,因为数据不在内存中,因此缓存管理器进行的是非缓存I/O,这表示这个I/O的读写都是直接通过硬盘进行的,而不需要将数据缓存到内存中。在Event 1中,为了从硬盘上读取数据,我们可以看到Event 1具有如下的堆栈记录:

在堆栈记录中,Explorer对于ReadFile的调用发生在frame 22中,使用的是BaseCopyStream函数,而缓存管理器通过访问该文件的内存映射直接获得了未缓存的读取操作,并在Frame 8导致了一个page fault。

因为Explorer通过持续访问的方式(在记录中无法察觉)打开这个文件,因此运行在System进程中的缓存管理器的read-ahead线程开始读取文件,这个操作的表现是Explorer的events 2和3。我们可以在Event 2的堆栈记录中看到read-ahead函数:

可能已经有人注意到了,为了顾及最初由Explorer读取导致的非缓存读取,read-ahead的读取最初是无序的,这有可能导致硬盘的磁头忙于寻道而降低性能。不过只要直接复制操作赶上了缓存管理器的读取进度,Explorer就会停止产生新的非缓存I/O,并从内存中复制剩余的内容。通常在进行复制操作的时候,缓存管理器会比Explorer领先128KB进行操作。

在上述追踪过程的Event 4中,Explorer进行了首次写入,随后我们才可以看到一系列交叉进行的读取和写入操作。在追踪的结尾处,同样是发生在System进程的缓存管理器后台写入线程中,用非缓存的写入操作将目标文件的数据从内存写入到硬盘中。

Vista在文件复制方面的改进

在Windows Vista的开发过程中,产品组重新设计了复制引擎,以便改善在不同场景下的复制性能。这个引擎最大的一个问题在于复制操作需要涉及到大量数据,目标系统上缓存管理器进行的后台写入操作通常无法跟上数据在内存中写入和缓存的速度。这会导致数据充满了内存,有可能导致其他有用的代码和数据被清理出内存,最后,目标系统的内存会成为一个通道,而所有被复制的数据流的速度都会被硬盘所限制。

另一个被注意到的问题是,当从远程系统进行复制时,文件的内容在本地系统上会被缓存两次:读取源文件的时候进行一次,写入目标文件的时候进行一次。这将导致客户端系统的内存中存储了可能不会被再次被访问的文件,这样缓存管理器必须消耗大量CPU资源以便在源文件和目标文件之间进行文件映射管理。

这种相当小的交叉进行的文件操作的一个局限在于SMB文件系统驱动,这个用于实现Windows远程文件共享协议的驱动无法将管道用于高带宽高延迟的网络,例如无线局域网中传输数据。每次本地系统都要等待来自远程系统的数据,通过网络的数据流耗尽后,复制操作就需要用额外的延迟以等待两个系统进行确认,并接着传输剩余的数据块。

在研究了不同的选择后,开发团队决定使用复制引擎以解决大量的异步非缓存I/O问题,并解决已经发现的所有问题。通过使用非缓存I/O,被复制的文件数据并不会占据本地系统的内存,因此可以保存内存中现有的内容。异步大文件I/O可以让数据管道通过高延迟的网络连接进行,而CPU占用率会因为缓存管理器不再管理自己的内存映射而降低,另外原始的Vista缓存管理器处理大I/O时低下的效率也促使产品组使用非缓存的I/O。然而这样做并不能让I/O变得太大,因为复制引擎需要在写入数据之前就将数据读取出来,而我们又需要并发的读取和写入,尤其是在不同硬盘或系统之间进行,因此过大的I/O使得提供准确的估算时间变得愈加困难,因为用于评估和更新估计时间所需的度量点实在是太少了。产品组当然意识到了非缓存I/O的不足,具体表现在:在复制大量小文件的时候,硬盘的磁头会不断移动,首先移动到源文件,随后移动到目标文件,然后移动到其他源文件,周而复始。

经过大量的分析、评估和调整,产品组使用了一种机制,为小于256KB的文件使用缓存I/O,但对于大于256KB的文件,引擎则使用内部队列(internal matrix)来判断一次需要进行的非缓存I/O的数量和文件大小。数量范围从2到8,分别对应了小于2MB到大于8MB的文件,对小于1MB的文件,I/O的大小就是文件本身大大小,大小介于1MB到2MB之间的文件的I/O大小统一是1MB,更大的文件则一直使用2MB的I/O大小。

举例来说,如果要复制一个16MB的文件,复制引擎会为源文件发起八个2MB的异步非缓存读取,等到这些I/O都发起后,对目标发起八个2MB的异步非缓存写入,等到一个写入I/O完成后再循环进行其他的读取和写入。下图就是从本地系统往远程系统复制一个16MB的文件时Process Monitor追踪到的整个过程:

虽然这种机制和早期使用的机制在很大程度上都有所提高,但依然存在一些不足。例如在对网络文件进行复制的时候偶尔就会出现无序(out-of-order)写操作,这种现象可以从下图中显示的接收端的追踪结果中看到:

从上图中可以看到,写操作的offset从327,680跳到了458,752,跳过了offset 393,216这一块。这种跳过现象会导致磁头寻道,并迫使NTFS文件系统执行一次不必要的写操作,将被跳过的这一块内容所在区域用”0″填充,因此对于offset 393,216这一块进行了两次写操作。在下图中我们可以看到,在上图选中的Event堆栈的追踪内容里,NTFS文件系统调用缓存管理器的CcZeroData函数将跳过的块用”0″填充:

使用非缓存I/O另一个更严重的问题是某些场合下极低的性能。例如,如果我们复制组成一个网站的大量文件,网站必须首先从硬盘上读取这些文件。虽然这种场景常见于服务器,不过很多时候甚至在客户端操作系统上也经常遇到这类情况,因为新出现的文件会促使搜索功能对文件创建索引,或则触发反病毒软件或反间谍软件进行扫描,另外Windows资源管理器的缩略图功能也需要为这些文件生成缩略图缓存。

也许这是这种机制最大的不足,同时这种机制也导致了大量的Windows Vista用户都在抱怨,复制一系列大小在256KB到数MB的文件时的系统性能和Windows XP相比要降低很多。这种现象主要是因为初始版本Windows Vista中使用的文件复制机制使用缓存的文件I/O。在老版本Windows中,复制机制可以让Explorer将目标文件写入到内存中,然后在缓存管理器的后台写入线程将缓存内容真正写入硬盘之前就关闭文件复制对话框;然而Vista使用了非缓存机制,Explorer被强制只能等待每个写操作都成功完成才能发起新的操作请求,最后只有等所有数据的副本都被写入硬盘后才表示这次复制操作成功完成。同时,在Windows Vista中,Explorer会在估计复制所需的时间之前等待12秒钟,而且使用的评估机制对于复制速度的波动非常敏感,在用户来看,这些情况都令复制的速度变得更慢。

SP1的改进

在Vista SP1的开发过程中,产品组决定重新设计复制引擎,以便能提高复制文件时的真实速度和表现速度。其中最大的改进在于重新对所有文件复制操作使用缓存的文件I/O,无论是本地还是远程操作,但还有一个例外,这个下文会简单介绍。通过使用缓存,用户可以感觉到的复制所需时间会变少,同时,无论是文件复制机制还是用于解决缓存I/O的缺点所需的平台都包含一些新的改进。

唯一在SP1中复制引擎不使用缓存的情况是远程文件复制,在这种情况下系统会通过调节Windows客户端远程文件系统驱动Rdbss.sys 的方法防止产生双缓存(double-caching)问题。这个功能是通过为该驱动提供一条命令,告诉驱动不要在远程系统上读取或写入远程文件的时候不要进行缓存实现的。我们可以在下面的Process Monitor截图中看到这个命令的使用情况:

远程复制方面的另一个改进则是SMB2文件系统驱动srv2.sys 所用的管道I/O,这是Windows Vista和Windows Server 2008中的新功能。在这种情况下,系统并不会像以前的SMB环境那样对跨越网络的文件发起60KB的I/O,SMB2会发起管道形式的64KB I/O,这样当系统从其他应用程序收到较大的I/O后,就可以发起多个64KB I/O序列,因此通过远程系统发送/接收的数据流延迟会更短。

另外复制引擎还会发起四个初始I/O,大小范围从128KB到1MB,具体大小则取决于要复制的文件的大小,这样可以让缓存管理器的read-ahead线程发起更大的I/O。SP1中对平台的改进使得缓存管理器可以为read-ahead和write-behind操作实现大I/O,而更大的I/O也更合理,因为相比原始版本的Vista I/O系统,现在已经可以支持大于64KB的I/O,这在之前版本的Windows中是无法实现的。更大的I/O同时可以改善本地复制的性能,因为所需的磁盘访问和寻道次数更少,而且对于要复制的数据,缓存管理器write-behind线程的速率更匹配数据往内存中填充的速率。这样不仅减少了不必要的时间估算操作,而且可以尽量避免因为复制过程中活动内存中的内容被丢弃给内存带来的额外负荷。最后,对于远程复制,这种大I/O可以让SMB2驱动使用管道,同时缓存管理器可以发起比应用程序发起的I/O大一倍的读取I/O,对于Windows Vista,最大可以达到2MB,而Server 2008最大可以达到16MB,同时Windows Vista的写入I/O可以达到1MB,Server 2008可以达到32MB。

下图的内容是从SP1系统给另外一个SP1系统复制16MB的文件时的过程,从中可以看到,Explorer发起了1MB的I/O,缓存管理器的read-ahead有2MB,这个可以从非缓存I/O的标记中看到:

然而,虽然SP1中的变化带来了比初始版本Windows Vista更好的性能,不过在某些特定情况下,性能可能还会比初始版本更低。首先就是通过慢速网络向/从Server 2003系统复制数据的时候,初始版本的Vista复制引擎可以实现高速复制,但因为上文中提到的无序I/O的问题,Server 2003中缓存管理器的触发行为有可能导致服务器的所有内存都被充满了要复制的数据。SP1中的复制引擎中的变化可以避免这种现象,但因为引擎会发起32KB的I/O而不是60KB的,因此在高延迟连接上可以实现的吞吐量可能只有初始版本Vista的一半。

另一个SP1可能不如初始版本做得好的情况是在同一个系统卷上进行大文件的复制。因为SP1会发起更小的I/O,这主要是为了能够让系统的其余组件可以更好地访问磁盘,并在复制操作的过程中提供更好的响应性能,因此从源文件读取直到写入到目标文件的这个过程中,磁头寻道的次数会增多,尤其是对于那些不包含内部队列机制的硬盘更是严重。

另外SP1的改进最后一个值得介绍的是Explorer估算所需时间的过程会比初始版本的Vista更快,而其估算的结果也更准确。

总结

文件复制过程并不像想的那么简单,但是产品组非常重视来自Vista用户的反馈,并且已经用了大量时间评估不同的方式,调整最后使用的方法,以便能够让大部分复制场景下Vista的性能至少能够和老版本的Windows差不多,同时还能够提高一些关键场景下的性能。这些改进不仅可以用于使用Explorer进行的复制,还可以用于所有支持CopyFileEx API的应用程序进行的复制。在通过高延迟高带宽的网络复制文件时,如果对方是老版本Windows,那么我们将看到最大的性能改进,因为这种情况下可以实现更大的I/O、SMB2的I/O管道,以及Vista的TCP/IP堆栈接收窗口自动调整功能,可以让在Windows XP或Server 2003下需要花10分钟完成的复制操作在Vista下只需要1分钟。相当酷。

分享:

《深入了解Vista SP1文件复制改进》上有13条评论

  1. 顺着您的space到这。能请教个问题吗?
    今天登陆hotmail,发现密码被盗。根据网页提示的两种方法想要找回密码。可是都办不到。第一种方法的问题是要提供密码提示问题的答案,可我发现密码提示问题已经被盗密码者更改。第二种方法是系统会发一封更改密码的电邮,可问题就是该电邮是发到密码已经被盗的这个电邮下,密码已经被盗,所以这个方法也不行。请问难道就没有别有别的办法了吗?谢谢!

  2. 这种情况还真没有遇到过,但密码提示问题和备用邮箱发送目前是唯一可用的两种方法。对于你这种情况,恐怕没有什么官方推荐的方法可以解决了。建议试试看联系一下客服,看看他们是不是有办法(我估计希望也不会太大):http://help.cn.msn.com/

  3. 刚才脑子没反映过来,还是你想得周到。

    今天年三十了,愿08工作顺利,身体健康,为我们大家带来更多的IT技术知识! 🙂

  4. 能请都个问题吗,我是想了解下microsoft的文件系统的文件复制过程的各种情况及底层的irp及应用层的一些具体的过程。另外要了解这些东西该怎么去查找资料呢。若百忙之中能看到我的留言,还请不吝赐教。谢谢。

  5. 刘晖 :抱歉最近事情太多,没能及时回复
    MS的一些规范和标准已经公开发布了,你可以在这里找找看是否有需要的东西:http://www.microsoft.com/openspecifications
    还有这里:http://msdn.microsoft.com/en-us/library/dd208104(v=PROT.10).aspx

    谢谢刘晖老师的热心回复。本来以为刘老师你太忙了,不会回复我的。看来刘老师真是一个热心的人。第 一次来看你的这篇文章一点也没有看懂。今天来看似乎懂了一些。小有一些收获。起码明白了我们最常用 的“explorer复制文件的一些细节的问题”包括一些关于对于缓存的利用。可以说得上是官方资料啊。
    谢谢啦!
    还有一个小小的问题想请教一下:我现在在做一个文件复制监控的东西,就是及时的记录对监控文件的一些操作情况。我想对复制行为这样来检测——在有被监控文件打开的时候对此进程的创建文件的行为进行监控,在这些新创建的文件关闭的时候获取一些文件的比如大小的信息,再都再读取一段文件内容进行比较。从而来进行复制监控。这些都在文件过滤驱动中做。您看下这样行吗。
    先谢谢啦。

  6. 不好意思,涉及到开发方面的内容我就不太懂了,没有研究过这些。不知道您具体想要实现怎样的目的呢?如果只是希望知道文件被访问的具体情况,使用Windows自带的审核策略就行了。但如果希望实现更高层次的应用,不妨具体说说看您的目的

  7. 其实我想做的一个功能比较的明确与简单,就是要对特定的文件进行监控。特别是对文件的读,还有就是怎么判断一个文件的内容的部分或者是全部(也就是我们一般说的拷贝)。比如被复制到了u盘上被给人带走了。或者是通过网络途径给传出去了。我该怎样去实现呢。现在是在文件过滤驱动上做文章。不晓得最后是否能达到目的。

  8. 好的,虽然没有实质性的指导,不过还是谢谢刘老师。我会自己去尝试的。文件系统的确挺烦的。很多的都不了解,特别的我又是初学。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

发表评论前,先做个简单的数学题吧: * Time limit is exhausted. Please reload CAPTCHA.