1 《Undocumented Windows 2000 Secrets》翻译 --- 第四章( 五 )


4KB 和 4MB 分页模型各有优缺点 。幸运的是,操作系统的设计人员不必非要在二者之中选择一个,可以混合使用这两种模型 。例如, Windows 2000 在内存范围 0x80000000 --- 0x9FFFFFFF 使用 4MB 大小的页,内核模块 hal.dll 和 ntoskrnl.exe 均被加载到该地址范围内 。剩余的线性地址采用 4KB 页来管理 。Intel 大力推荐采用这种混合设计,以改进系统性能,这也因为 4KB 和 4MB 的页项( Page Entry )都会被高速缓存到不同的转换后备缓冲区( Translation Lookaside Buffers , TLBs )中,该 TLB 位于 i386 CPU 内部( Intel 1999c , pp.3-22f ) 。操作系统的内核通常比较大,而且需要常驻内存,因此,如果将它们保存在多个 4KB 页中将会永久性的耗尽宝贵的 TLB 空间 。
图 4-4. 一层间接模型(采用 4MB 页)
注意,地址转换的所有步骤都在物理内存中进行 。PDBR 和所有的 PDE 、 PTE 包含的都是物理地址指针 。在 图 4-3 和 图 4-4 中可找到的线性地址位于左下角,该线性地址将转化为物理页中的偏移量 。另一方面,应用程序却必须使用线性地址,它们对物理地址一无所知 。不过,通过将页目录和其下属的所有页表映射到线性地址空间可以填补这一不足 。在 Windows 2000 和 Windows NT 4.0 中,在线性地址范围 0xC0000000----0xC03FFFFF 可访问所有的 PDE 和 PTE ,这是一个采用 4MB 页的线性内存区域 。可以简单的通过线性地址的高 20 位来查找与其相关联的 PTE ,这个高 20 位作为 32 位 PTE 数组的索引, PTE 数组起始于 0xC0000000。例如,地址 0x00000000 表示的 PTE 位于 0xC0000000。假定有一线性地址 0x80000000 ,通过将该地址右移 12 位,可得到 0x80000 (即该地址的高 20 位),因为每个 PTE 占用 4 个字节,所以目标 PTE 的地址为: 0xC0000000( 4*0x80000 ) =0xC0200000。这样的结果看起来很有趣,线性地址将 4GB 地址空间划分为相等的两部分,又映射为一个 PTE 的地址,从而将 PTE 数组也划分为了相等的两部分 。
现在,让我们更进一步,通过 PTE 自身来计算数据项在 PTE 数组中的地址 。常规的映射公式为:(( LinearAddress >> 12 ) *4 )0xC0000000。LinearAddress 取值范围为: 0xC0000000----0xC0300000。位于线性地址 0xC0300000 的数据项指向 PTE 数组在物理内存中的起始位置 。现在回去看一下 图 4-3 ,开始于地址 0xC0300000 的 1024 个数据项肯定是页目录!这种特殊的 PDE 、 PTE 排列方式被多个内存管理函数使用,这些函数由 ntoskrnl.exe 导出 。例如,有文档记载的 API 函数 MmIsAddressValid() 和 MmGetPhysicalAddress() 使用 32 位的线性地址来查找其 PDE ,如可用,还会查找其 PTE ,并会检查它们的内容 。MmIsAddressValid() 简单的检验目标页是否位于物理内存中 。如果测试失败,就意味着线性地址或者无效或者该地址引用的页已经被置换到了后备存储器(由系统页面文件集表示)中 。MmGetPhysicalAddress() 首先从线性地址中提取相应的页帧计数器( PFN ),该 PFN 就是与其相关的物理内存页(该页将按照页大小进行划分)的基地址 。接下来,它通过线性地址中剩余的 12 个位,来计算在物理页中的偏移量,最后将 PFN 指出的物理页基地址和前面算出的偏移量相加即可得到该线性地址对应的物理地址 。
更彻底的检查 MmGetPhysicalAddress() 的实现方式,会发现 Windows 2000 内存布局的另一个有趣的特性 。MmGetPhysicalAddress() 函数在开始之前,首先测试线性地址是否位于 0x80000000-----0x9FFFFFFF。就像前面提到的,这里存放着 hal.dll 和 ntoskrnl.exe ,而且这也是 Windows 2000 使用 4MB 页的地址块 。这个有趣的特性是,如果给定的线性地址位于这一范围, MmGetPhysicalAddress() 将不会关心所有的 PDE 或 PTE。替代的是,该函数简单的将线性地址的高 3 位设为零,然后加上字节偏移量,最后将得到地址作为物理地址返回 。这意味着,物理地址范围: 0x00000000----0x1FFFFFFF 将按照 1 : 1 的比例映射到线性地址 0x80000000----0x9FFFFFFF !要知道 ntoskrnl.exe 总是被加载到线性地址 0x80400000 ,这意味着 Windows 2000 的内核总位于物理地址 0x00400000 ,这种情况发生在第二个 4MB 页的基地址位于物理内存中 。事实上,通过检查这些内存区域可以证明上面的假定是正确的 。本章提供的 Memory SPY 将使你有机会看到这一点 。

推荐阅读