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


由页表提供的间接寻址方式蕴含着很有趣的两件事 。第一,程序所使用的地址和 CPU 使用的物理地址总线上的地址之间并没有预设的关系 。如果你知道你的程序所使用的数据结构位于某一地址,如, 0x00140000 ,你可能仍然不想知道任何有关这些数据的物理地址的信息,除非你要检查页表树( page-table tree ) 。这需要操作系统来决定这些地址之间的映射关系 。甚至当前有效的地址转换都是无法预测的,部分的来看,这是分页机制所固有的随机性导致的 。幸运的是,在大多 数应用程序中,并不需要有关物理地址的知识 。不过,对于开发硬件驱动程序的人员来说还是需要某些这方面的知识 。分页的另一个隐晦之处是:地址空间并不必须 是连续的 。实际上,根据页表的内容, 4GB 的空间可以包含大量的“空洞”,这些“空洞”既没有映射到物理内存也没有映射到后备存储器中 。如果一个应用程序试图读取或写入这样的一个地址,它将立即被系统中止掉 。稍后,我会详细的说明 Windows 2000 是如何将可用内存扩展到 4GB 地址空间的 。
80486 和 Pentium CPU 使用的分段和分页机制与 80386 很相似,但一些特殊的寻址特性除外,如 Pentium Pro 采用的物理地址扩展( Physical Address Extension, PAE )机制 。随同更高的时钟频率一起, Pentium CPU 的另一特性就是其采用的双重指令流水线,这一特性允许它在同一时刻执行两个操作(只要这两个指令不互相依赖) 。例如,如果指令 A 修改一个寄存器的值,而与其相邻的指令 B 需要这个修改后的值来进行计算,在 A 完成之前, B 将无法执行 。但是如果指令 B 使用另一个寄存器, CPU 就可同时执行这两个指令 。Pentium 系列 CPU 采用的多种优化方式为编译器的优化提供了广阔的空间 。如果你对这方面的话题很感兴趣,请参考 Rick 的《 Inner Loops 》( Booth 1997 ) 。
在 i386 的内存管理中,有三类地址非常有名,它们的术语 --- 逻辑、线性和物理地址出现在 Intel 的系统编程手册( Intel 1999c ) 。
1. 逻辑地址 :这是内存地址的精确描述,通常表示为 16 进制: xxxx:YYYYYYYY ,这里 xxxx 为 selector ,而 YYYYYYYY 是针对 selector 所选择的段地址的线性偏移量 。除了指定 xxxx 的具体数值外,还可使用具体的段寄存器的名字来替代之,如 CS (代码段), DS (数据段), ES (扩展段), FS (附加数据段 #1 ), GS (附加数据段 #2 )和 SS (堆栈段) 。这些符号都来自旧的“段 : 偏移量”风格,在 8086 实模式下使用此种方式来指定“ far pointers ”(远指针) 。
2. 线性地址 :大多数应用程序和内核驱动程序都忽略虚拟地址 。它们只对虚拟地址的偏移量部分感兴趣,而这一部分通常称为线性地址 。此种类型的地址假定了一种默认的分段模型,这种模型由 CPU 的当前段寄存器确定 。Windows 2000 使用 flat segmentation (平滑段),此时 CS 、 DS 、 ES 和 SS 寄存器都指向相同的线性地址空间;因此,程序可以认为所有的代码、数据和堆栈指针都可安全的相互转化 。例如,在任何时候,堆栈中的一个地址都可以转化为一个数据指针,而不需要关心相应段寄存器的值 。
3. 物理地址 :仅当 CPU 工作于分页模式时,此种类型的地址才会变得非常“有趣” 。本质上,一个物理地址是 CPU 插脚上可测量的电压 。操作系统通过设立页表将线性地址映射为物理地址 。Windows 2000 所用页表的布局的某些属性,对于调试软件开发人员非常有用,本章稍后将讨论之 。
虚拟地址和线性地址的差别多少有些人为的痕迹,在一些文档中会交替的使用这两个词 。我会尽力保证使用这一术语的一致性 。特别需要注意的是, Windows 2000 假定物理地址有 64 位宽 。而 Intel i386 系统通常只有一个 32 位的地址总线 。不过,某些 Pentium 系统支持大于 4GB 的物理内存 。例如,使用 PAE 模式的 Pentium Pro CPU ,这种 CPU 可以将物理地址扩展到 36 位,这样就可访问多大 64GB 的物理内存( Intel 1999c ) 。因此, Windows 2000 的 API 函数通常使用数据类型 PHYSICAL_ADDRESS 来表示物理地址, PHYSICAL_ADDRESS 实际是 LARGE_INTEGER 结构的别名,如 列表 4-1 所示 。这两种类型都定义在 DDK 头文件 ntdef.h 中 。LARGE_INTEGER 实际上是 64 位有符号整数的结构化表示,它可以被解释为一对 32 位数( LowPart 和 HighPart )或一个完整的 64 位数( QuadPart ) 。LONGLONG 类型等价于 Visual C/C的原生类型 __int64 ,该类型的无符号表示叫做 ULONGLONG 或 DWordLONG ,它们都依赖基本的无符号类型 __int64。

推荐阅读