在上一节《x86存储管理》中提到了线性地址,接下来为大家简单介绍一下线性地址。
多任务操作系统允许几个程序(任务)同时在内存中运行。每个程序都有自己唯一的数据区。假设现有 3 个程序,每个程序都有一个变量的偏移地址为 200h,那么,怎样区分这 3 个变量而不进行共享?
x86 解决这个问题的方法是,用一步或两步处理过程将每个变量的偏移量转换为唯一的内存地址。
第一步,将段值加上变量偏移量形成线性地址 (linear address)。这个线性地址可能就是该变量的物理地址。但是像 MS-Windows 和 Linux 这样的操作系统采用了分页 (paging) 功能,它使得程序能使用比可用物理空间更大的线性空间。这种情况下,就必需采用第二步页转换 (page translation),将线性地址转换为物理地址。
首先了解一下处理器如何用段和选择符来确定变量的线性地址。每个段选择符都指向一个段描述符(位于描述符表中),其中包含了该内存段的基地址。如下图所示,逻辑地址中的 32 位偏移量加上段基址就形成了 32 位的线性地址。
线性地址是一个 32 位整数,其范围为 0FFFFFFFFh,它表示一个内存位置。如果禁止分页功能,那么线性地址也就是目标数据的物 理地址。
分页是 x86 处理器的一个重要功能,它使得计算机能运行在其他情况下无法装入内存的一组程序。处理器初始只将部分程序加载到内存,而程序的其他部分仍然留在硬盘上。
程序使用的内存被分割成若干小区域,称为页 (page),通常一页大小为 4KB。当每个程序运行时,处理器会选择内存中不活跃的页面替换出去,而将立即会被请求的页加载到内存。
操作系统通过维护一个页目录 (page directory) 和一组页表 (page table) 来持续跟踪当前内存中所有程序使用的页面。当程序试图访问线性地址空间内的一个地址时,处理器会自动将线性地址转换为物理地址。这个过程被称为页转换 (page translation)。
如果被请求页当前不在内存中,则处理器中断程序并产生一个页故障 (page fault)。操作系统将被请求页从硬盘复制到内存,然后程序继续执行。从应用程序的角度看,页故障和页转换都是自动发生的。
使用 Microsoft Windows 工具任务管理器(task manager)就可以查看物理内存和虚拟内存的区别。如下图所示计算机的物理内存为 256MB。任务管理器的 Commit Charge 框内为当前可用的虚拟内存总量。虚拟内存的限制为 633MB,大大高于计算机的物理内存。
段描述符可以在两种表内找到:全局描述符表(global description table)和局部描述符表(local description table)。
全局描述符表(GDT)开机过程中,当操作系统将处理器切换到保护模式时,会创建唯——张 GDT,其基址保存在 GDTR(全局描述符表寄存器)中。表中的表项(称为段描述符)指向段。操作系统可以选择将所有程序使用的段保存在 GDT 中。
局部描述符表(LDT)在多任务操作系统中,每个任务或程序通常都分配有自己的段描述符表,称为 LDT。LDTR 寄存器保存的是程序 LDT 的地址。每个段描述符都包含了段在线性地址空间内的基地址。
一般,段与段之间是相互区分的。如下图所示,图中有三个不同的逻辑地址,这些地址选择了 LDT 中三个不同的表项。这里,假设禁止分页,因此, 线性地址空间也是物理地址空间。
除了段基址,段描述符还包含了位映射字段来说明段限长和段类型。只读类型段的一个例子就是代码段。如果程序试图修改只读段,则会产生处理器故障。
段描述符可以包含保护等级,以便保护操作系统数据不被应用程序访问。下面是对每个描述符字段的说明:
一个 32 位整数,定义段在 4GB 线性地址空间中的起始地址。
每个段都可以分配一个特权级,特权级范围从 0 到 3,其中 0 级为最高级,一般用于操作系统核心代码。如果特权级数值高的程序试图访问特权级数值低的段,则发生处理器故障。
说明段的类型并指定段的访问类型以及段生长的方向(向上或向下)。数据(包括堆栈)段可以是可读类型或读/写类型,其生长方向可以是向上的也可以是向下的。代码段可以是只执行类型或执行/只读类型。
这一位说明该段当前是否在物理内存中。
确定对段限长字段的解释。如果该位清零,则段限长以字节为单位。如果该 位置 1,则段限长的解释单位为 4096 字节。
这个 20 位的整数指定段大小。按照粒度标志,这个字段有两种解释: