嵌入式操作系统
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

5.2 IA32 CPU内存管理机制

5.2.1 IA32 CPU内存管理机制概述

IA32的内存管理机制由两部分组成:分段和分页。其中,分段提供了一种机制,使得应用程序或操作系统的数据、代码、堆栈等可以相互隔离,避免相互影响,在多任务(多进程)的情况下,每个任务都有自己特定的段,这样每个任务之间也不会相互影响。而分页机制则提供了按需内存分配、虚拟内存等机制,有了这些机制的支持,就可以实现应用程序的部分装入(应用程序执行映像只加载特定的部分到内存中就可以开始执行)等功能。当然,分页机制也可以用于应用程序之间的隔离(或保护)。在IA32体系构架的CPU中,是否启用分页机制是一个可选项,通过设置CR0寄存器(控制寄存器)的某一个比特,可以禁止或启用分页机制,而分段机制则不然,任何情况下都是启用的,没有一种方法可以禁止分段功能。

IA32 CPU提供的这种内存管理机制十分灵活。最简单的情况下,采用平展段模式,禁止分页,可以实现最简单的、与物理内存一样的内存管理模型;最复杂的情况下,采用独立的段管理不同进程(或操作系统)的不同数据(代码、数据、堆栈等),采用分页机制实现虚拟内存、按需内存分配等,可以实现最完整的程序保护,可以确保操作系统不受任何应用程序的影响,且应用程序之间也互不影响,而同一个应用程序内采用段保护机制,也不会出现堆栈溢出、非法访问等异常情况。

图5-1很清楚地表示了这种内存管理机制。

图5-1 Intel CPU(IA32)内存管理机制

从图5-1中可以看出,通过分段的方式,把CPU可寻址的整个地址空间(此处叫做线性地址空间)分成了若干部分,每部分对应一个段。要描述每个段,就需要知道这个段在线性地址空间中的基地址(起始地址)以及该段的长度(界限),对于不同的段(比如代码段、数据段等),还有不同的访问方式(只读、读写等),所有这些数据存放在一个段描述表中(图5-1中对应的GDT),段描述表的每一项(称为段描述符)描述了一个特定的段。要访问一个段内的特定字节,需要给出两个数据:该段的描述符(用以确定段的基地址)和该字节在段中的偏移(相对于段基地址)。在IA32的实现中,段描述符表存放在物理内存中,整个段描述符表的初始地址存放在一个特定的寄存器GDTR中,因此,在访问段描述符的时候,需要通过GDTR查找到描述符表对应的物理内存起始地址,然后再根据描述符在描述符表中的索引,定位到具体的段描述符的物理地址。段描述符在描述符表中的索引,称为段选择子。对于所有的段,由于GDTR是固定的,所以,给出一个段选择子,就可以准确定位到具体的段,也就是说,段描述符和段选择子是一一对应的。因此,要访问特定段内的一个字节,只需要给出一个段选择子和该字节在该段中的偏移位置即可。这两者的组合(段选择子和段内偏移)称为逻辑地址。在IA32的段寄存器(CS、DS等)中,存放的实际上就是段选择子。图5-2示意了逻辑地址和线性地址的关系。

图5-2 逻辑地址和线性地址的关系

给出一个逻辑地址后,CPU就根据段选择子查找到对应的段描述符,从段描述符中获得段的基地址(在线性地址空间中),然后把基地址跟逻辑地址的字节偏移部分相加,就获得一个线性地址。若没有启用分页机制,这个线性地址就可以直接映射到物理地址了。可以看出,这个过程是复杂的,需要多次访问物理内存,这样势必造成效率上的折扣。为了解决这个问题,IA32构架的CPU采用段寄存器来存储段选择子,在访问段内数据的时候,逻辑地址的选择子部分直接从段寄存器中获取。按照当前的实现,代码段选择子从CS寄存器内获得,数据段选择子从DS寄存器内获得,堆栈段选择子从SS寄存器内获得。因此,访问具体数据的时候需要根据数据所在的段,先把段选择子装入特定的段寄存器。为了进一步提高效率,IA32还实现了影子寄存器的机制。CS/DS等段寄存器还包含了不可见的影子部分,影子部分存储了当前段的起始地址和段界限,这些数据在初始化CS/DS等段寄存器的时候,由CPU统一初始化,这样在访问段内数据时,就不用再从物理内存中获取段描述符,然后再获得段基址了,而是直接从影子寄存器内获得段基址。在这样的机制下,访问一个段内的一个字节,只通过一次内存访问操作就完成了,大大提高了效率。段寄存器和影子寄存器的关系如图5-3所示。

图5-3 段寄存器和影子寄存器的关系