浅谈Windows 2000/XP File Cache实现
WebCrazy(http://webcrazy.yeah.net)
声明:本文所叙内容为Windows NT/2000/XP的Cache管理内容,涉及内容均为Microsoft Undocumented内容。我在参考大量资料后分析而成,牵涉内容多多少少可能会有些许错误,甚至根本就是错误的,但本着学习交流的目的,权当是写下的笔记公布于此(http://webcrazy.yeah.net),希望能多加于交流(tsu00@263.net),以便我以后更新错误。
记得早期dos下smartdrv.exe的功效就曾让我有较深的印象。我不记得我是在什么时候对这个小文件有兴趣且对其进行过学习的,这也是我对File Cache最初期的印象。在Windows NT/2000/XP中Cache管理部分更是让我有了现代操作系统实现File Cache的整体概念。读过《Inside Windows 2000》或是《Windows NT File System Internals》的肯定对此有比较深的理解,我不准备对他们已经涉及到的内容过于拘泥,我发现其实他们涉及到的许多东西并没有深入(虽然都有点出),或许也没有必要讲到具体实现上来,这会随着Windows的新版本的出现有些许改进变化。我得益于这两本书与其他许多资料,我也建议在往下看此文之前,首先好好的看一下他们。
我们知道Cache部分最主要的是对文件(含网络远程文件)等较费时的IO操作作高速缓存(置入相对较快的内存中),既然其操作的对象主要是File(Windows中很多东西在底层均是作为File对象的),我们的叙述也已FILE对象开始。一个FILE对象,均有一个SECTION_OBJECT_POINTERS定义(详细的定义请参阅ntddk.h或ntifs.h中的FILE_OBJECT)。我在《探寻Windows NT/2000 Copy On Write机制》中指出过其定义:
typedef struct _SECTION_OBJECT_POINTERS {
PVOID DataSectionObject; //Control Area
PVOID SharedCacheMap;
PVOID ImageSectionObject; //Control Area
} SECTION_OBJECT_POINTERS;
因为成员DataSectionObject与ImageSectionObject均指向一称为Control Area的内部结构,而Control Area结构又指向一个称为Segment的内部结构。Segment由一个或多个原型PTE(Prototype PTE,PPTE)组成。PPTE是一个软件结构,我在《小议Windows NT/2000分页机制》结尾处提及过PPTE,也提及过PPTE不同于那边介绍的常规硬件PTE。PPTE的一个目的即是实现页面共享。而一个或多个PPTE(s)则组成了Subsection。这些基于kernel debug的ca命令的输出结果分析而成。
成员SharedCacheMap则是真正的File Cache使用的结构。不同于FILE_OBJECT的另一个成员PrivateCacheMap,针对同一个文件,任何打开的指向这个文件的Handle指向的FILE_OBJECT结构的SharedCacheMap均是同一个值,这也是共享文件一个保证。我们来看在系统某一时刻同时指向shdocvw.dll的两个FILE_OBJECT(地址分别为80d37550与80db4860)的情况:
//指向shdocvw.dll的第一个FILE_OBJECT:
:fobj 80d37550
DeviceObject * : 80EE2888
Vpb * : 80EE2800
FsContext * : E14D2D90
FsContext2 * : E14D2EE8
SecObjPointer * : 80DD15F4
PrivateCacheMap * : 80EA70A0
.
.
.
FileName : /WINDOWS/system32/shdocvw.dll
.
.
.
//指向shdocvw.dll的第二个FILE_OBJECT:
:fobj 80db4860
DeviceObject * : 80EE2888
Vpb * : 80EE2800
FsContext * : E14D2D90
FsContext2 * : E1183678
SecObjPointer * : 80DD15F4
PrivateCacheMap * : 80D6F2D8
.
.
.
FileName : /WINDOWS/system32/shdocvw.dll
.
.
.
从以上Softice的结果我们很容易的发现SECTION_OBJECT_POINTERS指向同一个内存区域,所以SharedCacheMap肯定指向同一个值,而PrivateCacheMap则是两个全然不同的值,进一步分析,FsContext也指向同一个值,而FsContext2则不同,实际上在实现FSD时,这两个成员一个为File Control Buffer(参阅ntifs.h中的FSRTL_COMMON_FCB_HEADER定义),一个指向Context Control Buffer,具体的用法可参考ntifs中的fastfat或是cdfs的实现,不在本文的讨论范围。
分析过后FILE_OBJECT,我们可以得到一个结论:通过FILE_OBJECT我们可以得到这个FILE对象的一个或两个Control Area,而通过Control Area我们则可以得到Segment,既而即是Segment底下的Subsection与PPTEs,另外通过FILE_OBJECT我们也可以得到这文件所有实例的SharedCacheMap。事实上SharedCacheMap也有指针指向FILE_OBJECT,下面我会继续说明。
记得我很早前就在《分析Windows NT/2000堆内存与虚拟内存组织》中定义过这样一个结构:
typedef struct vad {
void *StartingAddress;
void *EndingAddress;
struct vad *ParentLink;
struct vad *LeftLink;
struct vad *RightLink;
ULONG Flags;
ULONG MMCI;
ULONG ProtoPTE;
}VAD, *PVAD;
实际上MMCI也即Control Area,我在《探寻Windows NT/2000 Copy On Write机制》中也作过解释。ProtoPTE即PPTE。
有了这样一个概念以后,我们即可以很容易的发现PPTE是如何实现页面共享的。系统在初次访问PPTE所指向的页面时,由于PPTE的bit 0为无效,所以发生Page Fault,从而交由int e处理,即控制权由ntoskrnl.exe中的KiTrap0E交由MmAccessFault处理,然后通过查VAD,即可通过Control Area与PPTE定位PFN Database,读取在磁盘的内容,更新PTE实现共享的目的。
详细的谈了FILE_OBJECT后,我们转向File Cache的讨论。Windows 2000/XP保留了由MmSystemCacheStart与MiSystemCacheStartExtra指向的两块系统虚拟内存区域专门用于System Cache。这专门的区域被分成大小为VACB_MAPPING_GRANULARITY(由ntifs.h中定义,值为0x40000,即256KB)的View。各View的使用情况由系统中的一个称为VACB的内部结构表示。这样系统中则有一个VACB数组,由CcVacbs指定。《Inside Windows 2000》中列举了VACB的四个成员,指出其包含一个重要的成员,即SharedCacheMap,下面是!dso命令的输出结果:
kd> !dso VACB //Kernel Debug Extension Build 2167 Free
Structure VACB - Size: 0x18
000 BaseAddress 004 SharedCacheMap
008 Overlay 010 LruList
但通过分析,我发现Windows XP中VACB实际上由6个DWORD值组成,其第二个DWORD即SharedCacheMap。Windows XP使用6个DWORD,可能是其支持Larger Mapped Files的具体实现(FILE_OFFSET使用LARGE_INTEGER,虽然《Inside Windows 2000》中也指出过FileOffset成员,但不知为什么dso命令的输出结果没有显式的提供这样的一个成员,另我非常疑惑,难道又是版本间的差异吗?)。下面是XP底下的分析:
kd> dd CcVacbs l 1
80542fec 80ecc000
kd> dd 80ecc000 //dd Vacb
80ecc000 cb440000 80eaac78 00300000 00000000
80ecc010 80eceda0 80ecf598 d4040000 80eaac78
80ecc020 01000000 00000000 80ecd6a8 80ecede8
80ecc030 c1100000 80eefed0 00000000 00000000
80ecc040 80ece050 80542fe0 deb40000 ffa55538
80ecc050 00000000 00000000 80ecd558 80ecc958
kd> dd 80eaac78 l 12 //dd Vacb->SharedCacheMap
80eaac78 013002ff 00000001 01a95400 00000000
80eaac88 80eaac68 80eaaac0 01b00000 00000000
80eaac98 ffffffff 7fffffff ffffffff 7fffffff
80eaaca8 00000000 00000000 00000000 00000000
80eaacb8 80eaa910 80ee2a80
上面我曾提及SharedCacheMap也有指针指向FILE_OBJECT,我发现其位置在的第18个DWORD上,下面即Dump出FILE_OBJECT。
kd> dd 80ee2a80 l 6 //dd FILE_OBJECT
80ee2a80 00700005 80ee2888 80ee2800 80e7f9b0
80ee2a90 00000000 80ee23a4
FILE_OBJECT的定义已在ntddk.h中给出,所以很容易的得到其SECTION_OBJECT_POINTERS。
kd> dd 80ee23a4 l 3 //dd FILE_OBJECT->SECTION_OBJECT_POINTERS
80ee23a4 80e7f710 80eaac78 00000000
kd> !ca 80e7f710 //!ca SECTION_OBJECT_POINTERS->DataSectionObject
ControlArea @80e7f710
Segment: e127a548 Flink 0 Blink 0
Section Ref 1 Pfn Ref 275 Mapped Views 5f
User Ref 0 WaitForDel 0 Flush Count 0
File Object 80ee2a80 ModWriteCount 0 System Views 5f
Flags (8088) NoModifiedWriting File WasPurged
File: /$Mft
.
.
.
这样我们即dump出了CcVacbs指向的System Cache中的第一个VACB的内容,从ca命令的输出结果我们可以看出System Cache的第一个View由NTFS的元数据$Mft使用。Kernel Debug也提供一个叫filecache的命令,我在使用!filecache命令时曾查阅过其文档,文档中提及(WinDbg:6.0.00007.0):
Each line of this extension's output represents a virtual address control block (VACB). When named files are mapped into the VACB, the names of these files are displayed. If "no name for file" is specified, this means that this VACB is being used to cache metadata.
从上面的注释我还以为filecache命令通过遍历VACB数组,dump出所有Control Area,就像我上面的手工分析VACB过程一样。但实际上此命令输出的不仅仅只有这些内容。通过分析kdexts.dll中filecache的实现(Windows XP)中,发现其通过PsLoadedModuleList来实现dump filecache(具体内容需进一步学习)。
最后还要指出的是系统通过CcIsFileCached宏来判断File是否被Cached。其在ntifs.h中定义,有了上面的分析后是非常容易理解的:
#define CcIsFileCached(FO) ( /
((FO)->SectionObjectPointer != NULL) && /
(((PSECTION_OBJECT_POINTERS)(FO)->SectionObjectPointer)->SharedCacheMap != NULL) /
)
本文未牵涉到另一个非常重要的数据结构PFN数据库,由MmPfnDatabase指定,PFN数据库中共享页面有PPTE的定义,实际上发生指向PPTE的页面的Page Fault后,查找完VAD后,系统会根据PFN数据库的PPTE完成下一步的工作。
实际上关于Control Area、Subsection、PPTE、VACB、SharedCacheMap等的具体定义是我比较感兴趣的,而我目前为止仍对此只有些许概念。这也是我出此文的目的,也希望达到抛砖引玉的作用(tsu00@263.net)。
WebCrazy(http://webcrazy.yeah.net)
声明:本文所叙内容为Windows NT/2000/XP的Cache管理内容,涉及内容均为Microsoft Undocumented内容。我在参考大量资料后分析而成,牵涉内容多多少少可能会有些许错误,甚至根本就是错误的,但本着学习交流的目的,权当是写下的笔记公布于此(http://webcrazy.yeah.net),希望能多加于交流(tsu00@263.net),以便我以后更新错误。
记得早期dos下smartdrv.exe的功效就曾让我有较深的印象。我不记得我是在什么时候对这个小文件有兴趣且对其进行过学习的,这也是我对File Cache最初期的印象。在Windows NT/2000/XP中Cache管理部分更是让我有了现代操作系统实现File Cache的整体概念。读过《Inside Windows 2000》或是《Windows NT File System Internals》的肯定对此有比较深的理解,我不准备对他们已经涉及到的内容过于拘泥,我发现其实他们涉及到的许多东西并没有深入(虽然都有点出),或许也没有必要讲到具体实现上来,这会随着Windows的新版本的出现有些许改进变化。我得益于这两本书与其他许多资料,我也建议在往下看此文之前,首先好好的看一下他们。
我们知道Cache部分最主要的是对文件(含网络远程文件)等较费时的IO操作作高速缓存(置入相对较快的内存中),既然其操作的对象主要是File(Windows中很多东西在底层均是作为File对象的),我们的叙述也已FILE对象开始。一个FILE对象,均有一个SECTION_OBJECT_POINTERS定义(详细的定义请参阅ntddk.h或ntifs.h中的FILE_OBJECT)。我在《探寻Windows NT/2000 Copy On Write机制》中指出过其定义:
typedef struct _SECTION_OBJECT_POINTERS {
PVOID DataSectionObject; //Control Area
PVOID SharedCacheMap;
PVOID ImageSectionObject; //Control Area
} SECTION_OBJECT_POINTERS;
因为成员DataSectionObject与ImageSectionObject均指向一称为Control Area的内部结构,而Control Area结构又指向一个称为Segment的内部结构。Segment由一个或多个原型PTE(Prototype PTE,PPTE)组成。PPTE是一个软件结构,我在《小议Windows NT/2000分页机制》结尾处提及过PPTE,也提及过PPTE不同于那边介绍的常规硬件PTE。PPTE的一个目的即是实现页面共享。而一个或多个PPTE(s)则组成了Subsection。这些基于kernel debug的ca命令的输出结果分析而成。
成员SharedCacheMap则是真正的File Cache使用的结构。不同于FILE_OBJECT的另一个成员PrivateCacheMap,针对同一个文件,任何打开的指向这个文件的Handle指向的FILE_OBJECT结构的SharedCacheMap均是同一个值,这也是共享文件一个保证。我们来看在系统某一时刻同时指向shdocvw.dll的两个FILE_OBJECT(地址分别为80d37550与80db4860)的情况:
//指向shdocvw.dll的第一个FILE_OBJECT:
:fobj 80d37550
DeviceObject * : 80EE2888
Vpb * : 80EE2800
FsContext * : E14D2D90
FsContext2 * : E14D2EE8
SecObjPointer * : 80DD15F4
PrivateCacheMap * : 80EA70A0
.
.
.
FileName : /WINDOWS/system32/shdocvw.dll
.
.
.
//指向shdocvw.dll的第二个FILE_OBJECT:
:fobj 80db4860
DeviceObject * : 80EE2888
Vpb * : 80EE2800
FsContext * : E14D2D90
FsContext2 * : E1183678
SecObjPointer * : 80DD15F4
PrivateCacheMap * : 80D6F2D8
.
.
.
FileName : /WINDOWS/system32/shdocvw.dll
.
.
.
从以上Softice的结果我们很容易的发现SECTION_OBJECT_POINTERS指向同一个内存区域,所以SharedCacheMap肯定指向同一个值,而PrivateCacheMap则是两个全然不同的值,进一步分析,FsContext也指向同一个值,而FsContext2则不同,实际上在实现FSD时,这两个成员一个为File Control Buffer(参阅ntifs.h中的FSRTL_COMMON_FCB_HEADER定义),一个指向Context Control Buffer,具体的用法可参考ntifs中的fastfat或是cdfs的实现,不在本文的讨论范围。
分析过后FILE_OBJECT,我们可以得到一个结论:通过FILE_OBJECT我们可以得到这个FILE对象的一个或两个Control Area,而通过Control Area我们则可以得到Segment,既而即是Segment底下的Subsection与PPTEs,另外通过FILE_OBJECT我们也可以得到这文件所有实例的SharedCacheMap。事实上SharedCacheMap也有指针指向FILE_OBJECT,下面我会继续说明。
记得我很早前就在《分析Windows NT/2000堆内存与虚拟内存组织》中定义过这样一个结构:
typedef struct vad {
void *StartingAddress;
void *EndingAddress;
struct vad *ParentLink;
struct vad *LeftLink;
struct vad *RightLink;
ULONG Flags;
ULONG MMCI;
ULONG ProtoPTE;
}VAD, *PVAD;
实际上MMCI也即Control Area,我在《探寻Windows NT/2000 Copy On Write机制》中也作过解释。ProtoPTE即PPTE。
有了这样一个概念以后,我们即可以很容易的发现PPTE是如何实现页面共享的。系统在初次访问PPTE所指向的页面时,由于PPTE的bit 0为无效,所以发生Page Fault,从而交由int e处理,即控制权由ntoskrnl.exe中的KiTrap0E交由MmAccessFault处理,然后通过查VAD,即可通过Control Area与PPTE定位PFN Database,读取在磁盘的内容,更新PTE实现共享的目的。
详细的谈了FILE_OBJECT后,我们转向File Cache的讨论。Windows 2000/XP保留了由MmSystemCacheStart与MiSystemCacheStartExtra指向的两块系统虚拟内存区域专门用于System Cache。这专门的区域被分成大小为VACB_MAPPING_GRANULARITY(由ntifs.h中定义,值为0x40000,即256KB)的View。各View的使用情况由系统中的一个称为VACB的内部结构表示。这样系统中则有一个VACB数组,由CcVacbs指定。《Inside Windows 2000》中列举了VACB的四个成员,指出其包含一个重要的成员,即SharedCacheMap,下面是!dso命令的输出结果:
kd> !dso VACB //Kernel Debug Extension Build 2167 Free
Structure VACB - Size: 0x18
000 BaseAddress 004 SharedCacheMap
008 Overlay 010 LruList
但通过分析,我发现Windows XP中VACB实际上由6个DWORD值组成,其第二个DWORD即SharedCacheMap。Windows XP使用6个DWORD,可能是其支持Larger Mapped Files的具体实现(FILE_OFFSET使用LARGE_INTEGER,虽然《Inside Windows 2000》中也指出过FileOffset成员,但不知为什么dso命令的输出结果没有显式的提供这样的一个成员,另我非常疑惑,难道又是版本间的差异吗?)。下面是XP底下的分析:
kd> dd CcVacbs l 1
80542fec 80ecc000
kd> dd 80ecc000 //dd Vacb
80ecc000 cb440000 80eaac78 00300000 00000000
80ecc010 80eceda0 80ecf598 d4040000 80eaac78
80ecc020 01000000 00000000 80ecd6a8 80ecede8
80ecc030 c1100000 80eefed0 00000000 00000000
80ecc040 80ece050 80542fe0 deb40000 ffa55538
80ecc050 00000000 00000000 80ecd558 80ecc958
kd> dd 80eaac78 l 12 //dd Vacb->SharedCacheMap
80eaac78 013002ff 00000001 01a95400 00000000
80eaac88 80eaac68 80eaaac0 01b00000 00000000
80eaac98 ffffffff 7fffffff ffffffff 7fffffff
80eaaca8 00000000 00000000 00000000 00000000
80eaacb8 80eaa910 80ee2a80
上面我曾提及SharedCacheMap也有指针指向FILE_OBJECT,我发现其位置在的第18个DWORD上,下面即Dump出FILE_OBJECT。
kd> dd 80ee2a80 l 6 //dd FILE_OBJECT
80ee2a80 00700005 80ee2888 80ee2800 80e7f9b0
80ee2a90 00000000 80ee23a4
FILE_OBJECT的定义已在ntddk.h中给出,所以很容易的得到其SECTION_OBJECT_POINTERS。
kd> dd 80ee23a4 l 3 //dd FILE_OBJECT->SECTION_OBJECT_POINTERS
80ee23a4 80e7f710 80eaac78 00000000
kd> !ca 80e7f710 //!ca SECTION_OBJECT_POINTERS->DataSectionObject
ControlArea @80e7f710
Segment: e127a548 Flink 0 Blink 0
Section Ref 1 Pfn Ref 275 Mapped Views 5f
User Ref 0 WaitForDel 0 Flush Count 0
File Object 80ee2a80 ModWriteCount 0 System Views 5f
Flags (8088) NoModifiedWriting File WasPurged
File: /$Mft
.
.
.
这样我们即dump出了CcVacbs指向的System Cache中的第一个VACB的内容,从ca命令的输出结果我们可以看出System Cache的第一个View由NTFS的元数据$Mft使用。Kernel Debug也提供一个叫filecache的命令,我在使用!filecache命令时曾查阅过其文档,文档中提及(WinDbg:6.0.00007.0):
Each line of this extension's output represents a virtual address control block (VACB). When named files are mapped into the VACB, the names of these files are displayed. If "no name for file" is specified, this means that this VACB is being used to cache metadata.
从上面的注释我还以为filecache命令通过遍历VACB数组,dump出所有Control Area,就像我上面的手工分析VACB过程一样。但实际上此命令输出的不仅仅只有这些内容。通过分析kdexts.dll中filecache的实现(Windows XP)中,发现其通过PsLoadedModuleList来实现dump filecache(具体内容需进一步学习)。
最后还要指出的是系统通过CcIsFileCached宏来判断File是否被Cached。其在ntifs.h中定义,有了上面的分析后是非常容易理解的:
#define CcIsFileCached(FO) ( /
((FO)->SectionObjectPointer != NULL) && /
(((PSECTION_OBJECT_POINTERS)(FO)->SectionObjectPointer)->SharedCacheMap != NULL) /
)
本文未牵涉到另一个非常重要的数据结构PFN数据库,由MmPfnDatabase指定,PFN数据库中共享页面有PPTE的定义,实际上发生指向PPTE的页面的Page Fault后,查找完VAD后,系统会根据PFN数据库的PPTE完成下一步的工作。
实际上关于Control Area、Subsection、PPTE、VACB、SharedCacheMap等的具体定义是我比较感兴趣的,而我目前为止仍对此只有些许概念。这也是我出此文的目的,也希望达到抛砖引玉的作用(tsu00@263.net)。