FSD 在第一次读取/写入文件时调用CcInitializeCacheMap
,这将导致创建私有缓存映射、部分对象、控制区域、段、子部分(如果它们尚不存在)。当缓存管理器创建部分对象时,它将指定它是NtCreateSection
sectionattributes 参数中的数据部分SEC_DATA,这意味着最初未配置 PPTE,并且段中的基留空。实际读取是使用CcCopyRead
完成的,首先分配 VACB 并映射文件视图,然后从 VACB 复制到缓冲区。每次分配 VACB 时,它都会将 265kb 的视图映射到缓存管理器虚拟空间。当它进行此映射时,它需要初始化一个 PPTE,但是当它初始化一个 PPTE 时,它不妨为整个文件初始化所有这些,因为它们在设计上需要连续;无论是否分配,都将保留该内存。
Windows内部声称它推迟为数据文件创建PPTE,直到映射第一个视图,但对于图像文件,它会在创建部分对象时创建它们。
对于页面文件支持的部分,首次创建部分对象时会创建一个原型 PTE 数组。对于映射的文件,在映射每个视图时,将按需创建数组的某些部分。
另一个消息来源指出:
在映射数据文件时,MiCreateDataFileMap 的主要目的是设置子节对象。在正常情况下,只创建一个小节,但在某些特殊条件下,使用多个小节,例如,如果文件非常大。对于数据文件,小节字段子小节库留空。这会延迟 PPTE 的创建,直到该部分映射到内存并最终首次访问。这背后的原因是为了避免在映射非常大的数据文件时浪费内存。相反,段对象的 SegmentPteTemplate 字段已正确设置,如有必要,可用于随后创建 PPTE。
我只是不同意这一点,因为如果映射一个 4GB 的数据文件,它只需要 2MB 的 PPTE 页面,这不是很多空间,所以我看不到这样做的好处。真正的空间节省来自 VACB 以及整个文件不必驻留在物理内存中的事实。
为什么他们声称PPTE的"部分"是按需初始化的,就好像它暗示它节省了空间一样,因为它只是没有,因为一旦映射了一个PPTE,整个区域就会被保留,并且SubsectionBase
被设置为整个文件的PPTE需要连续。
"部分"可以按需初始化,但它不会改变无论那里是否有PPTE都可以保留空间的事实。例如,当分配 VACB 时,它可以初始化覆盖 256kb 的所有 PPTE。发生页面错误时,PTE 指向 PPTE,PPTE 将无效,因此它可以在 256kb 粒度上执行 256kb 群集 IO,这意味着任何其他页面错误都将是软页面错误。(我相信,当第一次分配PPTE时,在为VACB视图分配PTE时,PTE指向PPTE。Windows 内部提出了一些关于错误虚拟地址的 bollock,用于在第 411 页上搜索进程的 PPTE 开始和结束,但系统缓存是内核内存的一部分,VAD 无法跟踪它,所以它是错误的。此方法仅用于用户空间映射)。这样做的一个问题是页面可能会被修改,因此它不一定能执行 256kb 群集优化(1 个硬页面错误,63 个软页面错误)并将页面锁定到内存中以执行 IO。它必须知道所有PPTE都是新分配的,错误不仅仅是因为单个帧不存在。最好的办法是在映射范围内的视图并分配 PPTE 时执行 IO,以便在读取发生时不会发生页面错误。否则,它将不得不处理 64 个硬页面错误。
那么有什么意义呢?它也可以在创建数据文件部分后立即初始化 PPTE 数组,因为它只会在上面的场景中立即读取。我想不出一个进程将文件加载映射到其地址空间然后不接触它们的情况。即使它映射了 40GB,它仍然只会占用 20MB 的 PPTE,因为它们是在创建数据部分时
初始化的。我想好处来自于这样一个事实,即保留的包含PPTE的虚拟范围实际上并没有映射到物理内存,尽管虚拟空间中的整个PPTE范围将被占用每个进程。映射 VACB 时,将填充该范围的 PPTE 部分,其中现在在物理内存中具有成本。但是,对于40GB的文件来说,它仍然只有20MB。20MB将保留在虚拟内存中,但0MB将保留在物理内存中。每 256KB 视图占用的物理空间量将增加 64*8 字节。