第四章 探索 Windows 2000 的内存管理机制
翻译: Kendiv( fcczj@263.ne t)
更新: Sunday, February 13, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
内存管理对于操作系统来说是非常重要的。本章将全面的纵览 Windows 2000 的内存管理机制以及 4GB 线性地址空间的结构。针对此部分内容,将解释 Intel i386 CPU 家族的虚拟内存寻址及其分页能力,重点将在于 Windows 2000 的内核是如何使用它们的。为了帮助我们对内存的探索,本章提供了一对程序:一个内核模式的驱动程序,该驱动用来收集系统相关的信息,另一个是用户模式的应用程序,该程序将通过设备 I/O 控制来查询来自驱动程序的数据,并在控制台窗口中进行显示。在剩余的章节中将重复使用“ Spy Driver ”模块来完成其他几个非常有趣的任务(这些任务都需要在内核模式下执行代码)。请坚持阅读完本章的第一部分,因为它将直接面对 CPU 硬件。不过,我仍然希望你不要跳过它,因为虚拟内存管理是一个非常令人兴奋的话题,理解它是如何工作的,将帮助你洞察复杂操作系统(如 Windows 2000 )采用的机制。
Intel i386 内存管理机制
Windows 2000 内核大量使用 Intel i386 CPU 系列提供的保护模式下的虚拟内存管理机制。为了更好的理解 Windows 2000 如何管理它的主内存,最低限度的熟悉 i386 CPU 的架构某些特点就显得尤为重要。 Windows 2000 是针对 Pentium 以上 CPU 设计的。不过,这些新的处理器采用的内存管理模型仍源自针对 80386 CPU 的设计,不过当然会加入了一些重要的增强。因此,微软通常标注 Windows NT 和 Windows 2000 的版本为 Intel 处理器“ i386 ”或者“ x86 ”。不要对这些感到困惑,不管你在本书的什么地方遇到 86 或 386 ,请记住,这只是表示特定的 CPU 架构,而不是特定的处理器版本。
基本的内存布局
Windows 2000 为应用程序和系统代码提供了非常简单的内存布局。由 32 位的 Intel CPU 提供的 4GB 虚拟内存空间被分割为相等的两部分。低于 0x80000000 的内存地址由用户模式下的模块使用,这包括 Win32 子系统,剩余的 2GB 保留给了系统内核。 Windows 2000 Advanced Server 还支持通常称为 4GT RAM Tuning 的另一种内存模型,该模型随 Windows NT 4.0 Server 的企业版引入。该模型可提供 3GB 的用户地址空间,另 1GB 保留给内核,通过在 boot.ini 中添加 /3GB 选项来启用该模型。
Windows 2000 Advanced Server 和 DataCenter 支持称为:物理地址扩展( Physical Address Extension, PAE )的内存选项,通过在 boot.ini 中加入 /PAE 就可允许这种内存方式。该选项采用了某些 Intel CPU 的特性(如, Pentium Pro 处理器)以允许大于 4GB 的物理内存映射到 32 位的地址空间上。在本章中,我将忽略这种特殊的设置。你可阅读微软的基本知识文章 Q171793 (微软 2000c )、 Intel 的 Pentium 手册( Intel 1999a,1999b,1999c )以及 Windows 2000 DDK 文档(微软 2000f )来获取更多此方面的信息。
内存分段和请求式分页
在深入 i386 架构的技术细节之前,想让我们回到 1978 年,那一年 Intel 发布了 PC 处理器之母: 8086 。我想将讨论限制到这个有重大意义的里程碑上。如果你打算知道更多,阅读 Robert L. 的 80486 程序员参考( Hummel 1992 )将是一个很棒的开始。现在看来这有些过时了,因为它没有涵盖 Pentium 处理器家族的新特性;不过,该参考手册中仍保留了大量 i386 架构的基本信息。尽管 8086 能够访问 1MB RAM 的地址空间,但应用程序还是无法“看到”整个的物理地址空间,这是因为 CPU 寄存器的地址仅有 16 位。这就意味着应用程序可访问的连续线性地址空间仅有 64KB ,但是通过 16 位段寄存器的帮助,这个 64KB 大小的内存窗口就可以在整个物理空间中上下移动, 64KB 逻辑空间中的线性地址作为偏移量和基地址(由 16 位的段寄存器给处)相加,从而构成有效的 20 位地址。这种古老的内存模型仍然被最新的 Pentium CPU 支持,它被称为:实地址模式,通常叫做:实模式。
80286 CPU 引入了另一种模式,称为:受保护的虚拟地址模式,或者简单的称之为:保护模式。该模式提供的内存模型中使用的物理地址不再是简单的将线性地址和段基址相加。为了保持与 8086 和 80186 的向后兼容, 80286 仍然使用段寄存器,但是在切换到保护模式后,它们将不再包含物理段的地址。替代的是,它们提供了一个选择器( selector ),该选择器由一个描述符表的索引构成。描述符表中的每一项都定义了一个 24 位的物理基址,允许访问 16MB RAM ,在当时这是一个很不可思议的数量。不过, 80286 仍然是 16 位 CPU ,因此线性地址空间仍然被限制在 64KB 。
1985 年的 80386 CPU 突破了这一限制。该芯片最终砍断了 16 位寻址的锁链,将线性地址空间推到了 4GB ,并在引入 32 位线性地址的同时保留了基本的选择器 / 描述符架构。幸运的是, 80286 的描述符结构中还有一些剩余的位可以拿来使用。从 16 位迁移到 32 位地址后, CPU 的数据寄存器的大小也相应的增加了两倍,并同时增加了一个新的强大的寻址模型。真正的 32 位的数据和地址为程序员带了实际的便利。事实上,在微软的 Windows 平台真正完全支持 32 位模型是在好几年之后。 Windows NT 的第一个版本在 1993 年 7 月 26 日发布,实现了真正意义上的 Win32 API 。但是 Windows 3.x 程序员仍然要处理由独立的代码和数据段构成的 64KB 内存片, Windows NT 提供了平坦的 4GB 地址空间,在那儿可以使用简单的 32 位指针来寻址所有的代码和数据,而不需要分段。在内部,当然,分段仍然在起作用,就像我在前面提及的那样。不过管理段的所有责任都被移给了操作系统。
80386 的另一个新特性是在硬件上支持分页,确切的来说是:请求式分页的虚拟内存。这种技术允许一个不同于 RAM 的存储介质 ---- 硬盘来为内存提供支持,例如,在允许分页时, CPU 通过将最近最少访问的内存数据置换到备份存储器中,从而为新的数据腾出空间,这样就能访问比可用物理内存更大的内存空间。理论上来说,可以使用此种方式访问 4GB 的连续线性地址空间,提供的备份介质必须足够的大 --- 即 使只安装了非常少的物理内存。当然,分页并不是访问内存的最快方式,最好还是能提供尽可能多的物理内存。但是,这是处理大量数据的最好办法,即使这些数据 超过了可用物理内存。例如,图形和数据库程序都需要一大块工作内存,如果没有分页机制的话,其中的某些程序就无法在低档的 PC 系统中运行。
80386 分页的模式是将内存划分为 4KB 或 4MB 大小的页。操作系统的设计者可以在二者之间自由的选择,也可混合使用这两个大小的页面。稍后,我会介绍 Windows 2000 采用的混合大小方案:由操作系统使用 4MB 的页面,而 4KB 页面由剩余的代码和数据使用。这些页面由分层结构的页表树管理,该页表树记录当前位于物理内存中的页,同时还记录了每个页是否实际的位于物理内存中。如果指定页已被置换到了硬盘上,而某些模块触及了位于这些页中的地址, CPU 就会产生一个缺页中断(这与外围硬件产生的中断类似)。接下来,位于操作系统内核中的缺页中断处理例程会试图将该页再次调入物理内存,这可能需要将另一块内存中的数据写入硬盘以腾出空间。通常,系统采用最近最少( LRU )算法来确定哪个页可以被置换出去。现在可以很清楚地看到为什么有时将这个过程称为 ---- 请求式分页( demand paging ):即,由软件提出请求,然后根据操作系统和应用程序使用的内存的统计数据,将物理内存中的数据移动到后备存储设备中。
由页表提供的间接寻址方式蕴含着很有趣的两件事。第一,程序所使用的地址和 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 。
图 4-1 给出了 i386 内存的分段模型,同时说明了逻辑地址和线性地址的关系。为了更清晰些,我将描述符表( descriptor table )和段( segment )画的比较小。实际上, 32 位的操作系统通常采用 图 4-2 所示的分段方案,这就是所谓的平滑内存模型( flat memory model ),它采用一个 4GB 大小的段。这种方案的不足是,描述符表变成了段的一部分,从而可以被有足够权限的代码访问到。
typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS;
typedef union _LARGE_INTEGER
{
struct
{
ULONG LowPart;
LONG HighPart;
};
LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;
列表 4-1. PHYSICAL_ADDRESS 和 LARGE_INTEGER 结构的定义
图 4-1. i386 的内存分段
图 4-2 给出的内存模型被 Windows 2000 作为标准的代码、数据和堆栈段,这意味着,所有的逻辑地址将包括 CS 、 DS 、 ES 和 SS 段寄存器。 FS 和 GS 的处理方式有所不同。 Windows 2000 并不使用 GS 寄存器,而 FS 寄存器被专门用来保存位于线性地址空间中的系统数据区域的基地址。因此, FS 的基地址远大于 0 ,其大小不会超过 4GB 。有趣的是, Windows 2000 为用户模式和内核模式分别维护两个不同的 FS 段。稍后我们将详细讨论这一问题。
图 4-2. 平滑的 4GB 内存段
在 图 4-1 和 图 4-2 中,逻辑地址的 selector 指向描述符表,该描述符表由名为 GDTR 的寄存器指定。这是 CPU 的全局描述符表寄存器,该寄存器可由操作系统设置为任何适当的线性地址。 GDT (全局描述符表)的第一项是保留的,该项对应的 selector 叫做“ null segment selector ”。 Windows 2000 将其 GDT 保存在 0x80036000 。 GDT 可容纳多达 8,19264 位的条目,即其最大值为 64KB 。 Windows 2000 仅使用开始的 128 个项,并将 GDT 的大小限制为 1,024 字节。随 GDT 一起, i386 CPU 还提供了一个本地描述符表( Local Descriptor Table , LDT )和一个中断描述符表( Interrupt Descriptor Table , IDT ),这两个表的起始地址分别保存在 LDTR 和 IDTR 这两个寄存器中。 GDTR 和 IDTR 的值是唯一的, CPU 执行的每个任务都采用相同的值,而 LDTR 的值则是任务相关的, LDTR 可容纳一个 16 位的 selector 。
图 4-3 示范了复杂的线性地址与物理地址的转换机制,如果在 4KB 分页模式下,并允许请求式分页, i386 的内存管理单元就会采用此种转换机制。图中左上角的页目录基址寄存器( Page-Directory Base Register , PDBR )包含页目录的物理地址。 PDBR 由 i386 的 CR3 寄存器保存。仅用该寄存器的高 20 位来寻址。因此,页目录也是以页为边界的。 PDBR 的剩余位作为标志位或保留以便将来扩展使用。页目录占用一个完整的 4KB 页,由包含 1024 个页目录项( Page-Directory Entry )的数组构成,每个页目录项均为 32 位。和 PDBR 类似,每个 PDE 被划分为一个 20 位的页帧计数器( Page-Frame Number , PFN )和一个标志数组。 PFN 用来寻址页表。每个页表都是按页对齐的,包含 1024 个页表项( Page-Table Entry , PTE )。每个 PTE 的高 20 位作为一个指针指向一个 4KB 的数据页。通过将线性地址分为三段来实现地址转换:高 10 位用来选择一个 PDE (属于页目录),接下来的 10 个位选择前面所选的 PDE 中的某个 PTE ,最后剩下的 12 个位用来指定在数据页中的偏移量,该数据页由前面所选的 PTE 确定。
图 4-3. 两层间接模型(采用 4KB 页)
在 4MB 分页模式下,事情就变得很简单了,这是因为消除了一个间接层,如 图 4-4 所示。此时, PDBR 仍然指向页目录,但仅使用了每个 PDE 的高 10 位,这是因为目标地址采用 4MB 对齐。因为没有使用页表,这个地址同样也是 4MB 数据页的基地址。所以,此时的线性地址只包含两个部分: 10 个位用来选择 PDE ,其余的 22 位作为偏移量。 4MB 内存方案的开销没有 4KB 那么大,这是因为仅页目录需要附加的内存。这 1024 个 PDE 中的每个都可寻址一个 4MB 页。这足够覆盖整个 4GB 地址空间了。所以, 4MB 分页的优势就是可以降低内存管理的开销,但结果就是寻址粒度较大。
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 将使你有机会看到这一点。
补充:
这部分内容选择自《 Windows 环境下 32 位汇编语言程序设计》
x86 的内存分页机制
当 x86 CPU 工作在保护模式和虚拟 8086 模式时,可以使用全部 32 根地址线访问 4GB 的内存。因为 80386 的所有通用寄存器都是 32 位的,所以用任何一个通用寄存器来间接寻址,不必分段就可以访问到 4GB 的内存地址。
但 这并不意味着,此时段寄存器就不再有用了。实际上,段寄存器更加有用了,虽然在寻址上没有分段的限制了,但在保护模式下,一个地址空间是否可以被写入,可 以被多少优先级的代码写入,是不是允许执行等等涉及保护的问题就出来了。要解决这些问题,必须对一个地址空间定义一些安全上的属性。段寄存器这时就派上了 用场。但是设计属性和保护模式下段的其他参数,要表示的信息太多了,要用 64 位长的数据才能表示。我们把这 64 位的属性数据叫做段描述符( Segment Descriptor )。
80386 的段寄存器是 16 位的,无法放下保护模式下 64 位的段描述符。如何解决这个问题呢?方法是把所有段的段描述符顺序存放在内存中的指定位置,组成一个段描述符表( Descriptor Table );而段寄存器中的 16 位用来做索引信息,指定这个段的属性用段描述符表中的第几个描述符来表示。这时,段寄存器中的信息不再是段地址了,而是段选择器( Segment Selector )。可以通过它在段描述符表中“选择”一个项目已得到段的全部信息。
那么段描述符表存放在哪里呢? 80386 引入了两个新的寄存器来管理段描述符表。一个是 48 位的全局描述符表寄存器 GDTR ,一个是 16 位的局部描述符表寄存器 LDTR 。那么,为什么有两个描述符表寄存器呢?
GDTR 指向的描述符表为全局描述符表 GDT ( Global Descriptor Table )。它包含系统中所有任务都可用的段描述符,通常包含描述操作系统所使用的代码段、数据段和堆栈段的描述符及各任务的 LDT 段等。全局描述符表只有一个。
LDTR 指向局部描述符表 LDT ( Local Descriptor Table )。 80386 处理器设计成每个任务都有一个独立的 LDT 。它包含每个任务私有的代码段、数据段和堆栈段的描述符,也包含该任务所使用的一些门描述符,如任务门和调用门描述符等。
不同任务的局部描述符分别组成不同的内存段,描述这些内存段的描述符当作系统描述符放在全局描述符表中。和 GDTR 直接指向内存地址不同, LDTR 和 CS 、 DS 等段选择器一样只存放索引值,指向局部描述符内存段对应的描述符在全局描述符表中的位置。随着任务的切换,只要改变 LDTR 的值,系统当前的局部描述符表 LDT 也随之切换,这样便于个任务之间数据的隔离。但 GDT 并不随着任务的切换而切换。
16 位的段选择器如何使用全局描述符表和局部描述符表这两个表呢?实际上,段选择器中只有高 13 位表示索引值。剩下的 3 个数据位中,第 0 , 1 位表示程序的当前优先级 RPL ;第 2 位 TI 位用来表示在段描述符的位置; TI=0 表示在 GDT 中, TI=1 表示在 LDT 中。
80386 处理器把 4KB 大小的一块内存当作一“页”内存,每页物理内存可以根据“页目录”和“页表”,随意映射到不同的线性地址上。这样,就可以将物理地址不连续的内存的映射连到一起,在线性地址上视为连续。在 80386 处理器中,除了与 CR3 (保存当前页目录的地址)相关的指令使用的是物理地址外,其他所有指令都是使用线性地址寻址的。
是否启用内存分页机制是由 80386 处理器新增的 CR0 寄存器中的位 31 ( PG 位)决定的。如果 PG=0 ,则分页机制不启用,这时所有指令寻址的地址(线性地址)就是系统中实际的物理地址;当 PG=1 的时候, 80386 处理器进入内存分页管理模式,所有的线性地址要经过页表的映射才得到最后的物理地址。
标签: