跳转至

第四章 80X86保护模式及其编程

  • 本书介绍的Linux 操作系统基于Intel公司 80X86 及相关外围硬件组成的 PC 机系统。有关 80X86 CPU 系统编程的最佳参考书籍当然是Intel公司发行的一套三卷的《IA-32 Intel体系结构软件开发者手册》,尤其是其中第3卷:《系统编程指南》是理解使用80X86CPU的操作系统工作原理或进行系统编程必不可少的参考资料。

  • 本章主要内容包括:1.80X86基础知识;2.保护模式内存管理;3.各种保护措施;4.中断和异常处理;5.任务管理;6.保护模式编程的初始化;7.一个简单的多任务内核例子。

4.1 80X86 系统寄存器和系统指令(4.1节看不懂!!

  • 标志寄存器EFLAGS中的系统标志和IOPL字段用于控制I/O访问、可屏蔽硬件中断、调试、任务切换以及虚拟-8086 模式,见下图所示。通常只允许操作系统代码有权修改这些标志。EFLAGS 中的其他标志是一些通用标志(进位 CF、奇偶 PF、辅助进位 AF、零标志 ZF、负号 SF、方向 DF、溢出 OF)。这里我们仅对EFLAGS 中的系统标志进行说明

4-1.png

  • TF 位 8 是跟踪标志(Trap Flag)

当设置该位时可为调试操作启动单步执行方式;复位时则禁止单步执行。在单步执行方式下,处理器会在每个指令执行之后产生一个调试异常,这样我们就可以观察执行程序在执行每条指令后的状态。如果程序使用 POPF、POPFD 或IRET指令设置了 TF 标志,那么在随后指令之后处理器就会产生一个调试异常。

  • IOPL 位 13-12 是I/O 特权级(I/O Privilege Level)字段。

该字段指明当前运行程序或任务的I/O 特权级 IOPL。当前运行程序或任务的CPL必须小于等于这个IOPL才能访问I/O地址空间。只有当CPL 为特权级0时,程序才可以使用 POPF 或IRET 指令修改这个字段。IOPL也是控制对IF标志修改的机制之一。

  • NT 位 14是嵌套任务标志(Nested Task)

它控制着被中断任务和调用任务之间的链接关系。在使用 CALL 指令、中断或异常执行任务调用时,处理器会设置该标志。在通过使用IRET指令从一个任务返回时,处理器会检查并修改这个NT标志。使用POPF/POPFD指令也可以修嘎这个标志,但是在应用程序中改变这个标志的状态会产生不可意料的异常。

  • RF 位 16 是恢复标志(Resume Flag)

该标志用于控制处理器对断点指令的响应。当设置时,这个标志会临时禁止断点指令产生的调试异常;当该标志复位时,则断点指令将会产生异常。RF 标志的主要功能是允许在调试异常之后重新执行一条指令。当调试软件使用IRETD指令返回被中断程序之前,需要设置堆栈上EFLAGS内容中的RF标志,以防止指令断点造成另一个异常。处理器会在指令返回之后自动地清除该标志,从而再次允许指令断点异常。

  • VM 位17是虚拟-8086方式(Virtual-8086Mode)标志。

当设置该标志时,就开启虚拟-8086方式;当复位该标志时,则回到保护模式。

4.1.2 内存管理寄存器

  • 处理器提供了4个内存管理寄存器(GDTR、LDTR、IDTR和 TR),用于指定分段内存管理所使用的系统表的基地址,如下图所示。处理器为这些寄存器的加载和保存提供了特定的指令。有关系统表的作用请参见下一节“保护模式内存管理”中的详细说明。

4-2.png

  • GDTR、LDTR、IDTR 和TR都是段基址寄存器,这些段中含有分段机制的重要信息表。GDTR、IDTR 和LDTR 用于寻址存放描述符表的段。TR 用于寻址一个特殊的任务状态段 TSS(Task State Segment)。TSS 段中包含着当前执行任务的重要信息。

  • 全局描述符表寄存器GDTR

  • GDTR 寄存器中用于存放全局描述符表GDT的 32 位线性基地址和16位表长度值。基地址指定GDT 表中字节0在线性地址空间中的地址,表长度指明GDT表的字节长度值。指令LGDT和 SGDT分别用于加载和保存GDTR寄存器的内容。在机器刚加电或处理器复位后,基地址被默认地设置为0,而长度值被设置成 0xFFFF。在保护模式初始化过程中必须给GDTR加载一个新值。

  • 中断描述符表寄存器IDTR

  • 与 GDTR的作用类似,IDTR寄存器用于存放中断描述符表IDT的 32 位线性基地址和 16位表长度值。指令LIDT和SIDT分别用于加载和保存IDTR寄存器的内容。在机器刚加电或处理器复位后,基地址被默认地设置为0,而长度值被设置成0xFFFF。

  • 局部描述符表寄存器LDTR

  • LDTR寄存器中用于存放局部描述符表LDT的32位线性基地址、16位段限长和描述符属性值。指令 LLDT 和 SLDT 分别用于加载和保存LDTR 寄存器的段描述符部分。包含LDT表的段必须在 GDT表中有一个段描述符项。当使用LLDT指令把含有LDT 表段的选择符加载进LDTR 时,LDT 段描述符的段基地址、段限长度以及描述符属性会被自动地加载到LDTR 中。当进行任务切换时,处理器会把新任务LDT 的段选择符和段描述符自动地加载进LDTR中。在机器加电或处理器复位后,段选择符和基地址被默认地设置为0,而段长度被设置成 0xFFFF。

  • 任务寄存器 TR

  • TR寄存器用于存放当前任务TSS段的 16 位段选择符、32位基地址、16位段长度和描述符属性值。它引用GDT表中的一个TSS类型的描述符。指令LTR和 STR分别用于加载和保存TR寄存器的段选择符部分。当使用LTR指令把选择符加载进任务寄存器时,TSS描述符中的段基地址、段限长度以及描述符属性会被自动地加载到任务寄存器中。当执行任务切换时,处理器会把新任务TSS的段选择符和段描述符自动地加载进任务寄存器TR中。

4.1.3 控制寄存器

  • 控制寄存器(CRO、CR1、CR2 和CR3)用于控制和确定处理器的操作模式以及当前执行任务的特性,见图 4-3所示。CR0 中含有控制处理器操作模式和状态的系统控制标志;CR1保留不用;CR2含有导致页错误的线性地址。CR3中含有页目录表物理内存基地址,因此该寄存器也被称为页目录基地址寄存器PDBR(Page-Directory Base address Register)。

4-3.png

  1. CR0 中协处理器控制位

  2. CR0 的4个比特位:扩展类型位ET、任务切换位 TS、仿真位EM 和数学存在位MP用于控制80X86 浮点(数学)协处理器的操作。有关协处理器的详细说明请参见第11章内容。CR0的ET位(标志)用于选择与协处理器进行通信所使用的协议,即指明系统中使用的是80387还是80287协处理器。TS、MP 和 EM位用于确定浮点指令或WAIT指令是否应该产生一个设备不存在DNA(Device Not Available)异常。这个异常可用来仅为使用浮点运算的任务保存和恢复浮点寄存器。对于没有使用浮点运算的任务,这样做可以加快它们之间的切换操作。

  3. ET CR0 的位 4是扩展类型(Extension Type)标志。

当该标志为1时,表示指明系统有 80387协处理器存在,并使用 32 位协处理器协议。ET=0 指明使用 80287 协处理器。如果仿真位EM=1,则该位将被忽略。在处理器复位操作时,ET位会被初始化指明系统中使用的协处理器类型。如果系统中有 80387,则ET 被设置成1,否则若有一个 80287 或者没有协处理器,则ET被设置成0。

  • TS CR0 的位3是任务已切换(TaskSwitched)标志。

该标志用于推迟保存任务切换时的协处理器内容,直到新任务开始实际执行协处理器指令。处理器在每次任务切换时都会设置该标志,并且在执行协处理器指令时测试该标志。

如果设置了TS标志并且CR0的EM标志为0,那么在执行任何协处理器指令之前会产生一个设备不存在 DNA(Device Not Available)异常。如果设置了TS标志但没有设置CR0的MP和EM标志,那么在执行协处理器指令WAIT/FWAIT之前不会产生设备不存在异常。如果设置了EM标志,那么 TS 标志对协处理器指令的执行无影响。见表 4-1所示。

在任务切换时,处理器并不自动保存协处理器的上下文,而是会设置 TS标志。这个标志会使得处理器在执行新任务指令流的任何时候遇到一条协处理器指令时产生设备不存在异常。设备不存在异常的处理程序可使用 CLTS 指令清除 TS 标志,并且保存协处理器的上下文。如果任务从没有使用过协处理器,那么相应协处理器上下文就不用保存。

  • EM CR0的位2是仿真(EMulation)标志。当该位设置时,表示处理器没有内部或外部协处理器,执行协处理器指令时会引起设备不存在异常;当清除时,表示系统有协处理器。设置这个标志可以迫使所有浮点指令使用软件来模拟。

  • MP CR0的位1是监控协处理器(Monitor Coprocessor 或Math Present)标志

用于控制WAIT/FWAIT 指令与 TS 标志的交互作用。如果 MIP=1、TS=1,那么执行 WAIT 指令将产生一个设备不存在异常;如果 MP=0,则 TS 标志不会影响 WAIT 的执行。

4-4.png


  1. CRO 中保护控制位

  2. PE CR0 的位0是启用保护(Protection Enable)标志

当设置该位时即开启了保护模式;当复位时即进入实地址模式。这个标志仅开启段级保护,而并没有启用分页机制。若要启用分页机制,那么 PE 和 PG 标志都要置位。

  • PG CR0的位31是分页(Paging)标志。当设置该位时即开启了分页机制;当复位时则禁止分页机制, 此时所有线性地址等同于物理地址。在开启这个标志之前必须已经或者同时开启PE 标志。即若要启用分页机制,那么 PE和PG标志都要置位。

  • WP 对于Intel 80486 或以上的CPU,CR0的位 16 是写保护(Write Proctect)标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作;当该位复位时则反之。该标志有利于 UNIX类操作系统在创建进程时实现写时复制(Copy on Write)技术。

-NE 对于Intel80486 或以上的CPU,CR0 的位5是协处理器错误(Numeric Error)标志。当设置该标志时,就启用了X87协处理器错误的内部报告机制;若复位该标志,那么就使用PC机形式的 X87协处理器错误报告机制。当NE为复位状态并且CPU的IGNNE 输入引脚有信号时,那么数学协处理器X87错误将被忽略。当NE 为复位状态并且CPU 的IGNNE 输入引脚无信号时,那么非屏蔽的数学协处理器X87错误将导致处理器通过FERR引脚在外部产生一个中断,并且在执行下一个等待形式浮点指令或WAIT/FWAIT 指令之前立刻停止指令执行。CPU的 FERR引脚用于仿真外部协处理器 80387 的ERROR 引脚,因此通常连接到中断控制器输入请求引脚上。NE 标志、IGNNE 引脚和 FERR引脚用于利用外部逻辑来实现 PC 机形式的外部错误报告机制。

  • 启用保护模式PE(Protected Enable)位(位0)和开启分页PG(Paging)位(位31)分别用于控制分段和分页机制。PE用于控制分段机制。如果PE=1,处理器就工作在开启分段机制环境下,即运行在保护模式下。如果 PE=0,则处理器关闭了分段机制,并如同 8086工作于实地址模式下。PG 用于控制分页机制。如果 PG=1,则开启了分页机制。如果 PG=-0,分页机制被禁止,此时线性地址被直接作为物理地址使用。

  • 如果 PE=0、PG=0,处理器工作在实地址模式下;如果 PG=0、PE=1,处理器工作在没有开启分页机制的保护模式下;如果 PG=1、PE=0,此时由于不在保护模式下不能启用分页机制,因此处理器会产生一个一般保护异常,即这种标志组合无效;如果 PG=1、PE=1,则处理器工作在开启了分页机制的保护模式下。

  • 当改变PE和PG位时,我们必须小心。只有当执行程序起码有部分代码和数据在线性地址空间和物 理地址空间中具有相同地址时,我们才能改变PG位的设置。此时这部分具有相同地址的代码在分页和未分页世界之间起着桥梁的作用。无论是否开启分页机制,这部分代码都具有相同的地址。另外,在开启分页(PG=1)之前必须先刷新CPU中的页高速缓冲(或称为转换查找缓冲区TLB-Translation Lookaside Buffers)。

  • 在修改该了PE位之后程序必须立刻使用一条跳转指令,以刷新处理器执行管道中已经获取的不同模 式下的任何指令。在设置PE位之前,程序必须初始化几个系统段和控制寄存器。在系统刚上电时,处理器被复位成PE=0、PG=0(即实模式状态),以允许引导代码在启用分段和分页机制之前能够初始化这些寄存器和数据结构。


  1. CR2 和 CR3

  2. CR2 和 CR3用于分页机制。CR3含有存放页目录表页面的物理地址,因此 CR3 也被称为 PDBR。因为页目录表页面是页对齐的,所以该寄存器只有高 20位是有效的。而低12位保留供更高级处理器使用,因此在往 CR3 中加载一个新值时低 12 位必须设置为 0。

  3. 使用MOV指令加载CR3时具有让页高速缓冲无效的副作用。为了减少地址转换所要求的总线周期数 量,最近访问的页目录和页表会被存放在处理器的页高速缓冲器件中,该缓冲器件被称为转换查找缓冲区 TLB(Translation Lookaside Buffer)。只有当 TLB 中不包含要求的页表项时才会使用额外的总线周期从内存中读取页表项。

  4. 即使CR0中的 PG位处于复位状态(PG=0),我们也能先加载 CR3。以允许对分页机制进行初始化。当切换任务时,CR3的内容也会随之改变。但是如果新任务的CR3值与原任务的一样,处理器就无需刷新页高速缓冲。这样共享页表的任务可以执行得更快。

  5. CR2用于出现页异常时报告出错信息。在报告页异常时,处理器会把引起异常的线性地址存放在CR2 中。因此操作系统中的页异常处理程序可以通过检查CR2的内容来确定线性地址空间中哪一个页面引发了异常。

4.1.4 系统指令

系统指令用于处理系统级功能,例如加载系统寄存器、管理中断等。大多数系统指令只能由处于特权级0的操作系统软件执行,其余一些指令可以在任何特权级上执行,因此应用程序也能使用。表4-2 中列出了我们将用到的一些系统指令。其中还指出了它们是否受到保护。

4-5.png

4-6.png

4.2 保护模式内存地址

4.2.1 内存寻址

  • 内存是指一组有序字节组成的数组,每个字节有唯一的内存地址。内存寻址则是指对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串。80X86支持多种数据类型:1字节、2字节(1个字)或4字节(双字或长字)的无符号整型数或带符号整型数,以及多字节字符串等。通常字节中某一比特位的定位或寻址可以基于字节来寻址,因此最小数据类型的寻址是对1字节数据(数值或字符)的定位。通常内存地址从0开始编址,对于80X86CPU来说,其地址总线宽度为32 位,因此一共有 2^32个不同物理地址。即内存物理地址空间有 4G,总共可以寻址4G字节的物理内存。对于多字节数据类型(例如2字节整数数据类型),在内存中这些字节相邻存放。 80X86首先存放低值字节,随后地址处存放高值字节。因此 80X86CPU是一种先存小值(Little Endium) 的处理器。

  • 对于 80X86 CPU,一条指令主要由操作码(Opcode)和操作对象即操作数(Oprand)构成。操作数可以位于一个寄存器中,也可以在内存中。若要定位内存中的操作数,就要进行内存寻址。80X86有许多指令的操作数涉及内存寻址,并且针对所寻址对象数据类型的不同,也有很多不同的寻址方案可供选择。

  • 为了进行内存寻址,80X86使用了一种称为段(Segment)的寻址技术。这种寻址技术把内存空间分成一个或多个称为段的线性区域,从而对内存中一个数据对象的寻址就需要使用一个段的起始地址(即段地址)和一个段内偏移地址两部分构成。段地址部分使用16位的段选择符指定,其中14位可以选择2~14次方即16384个段。段内偏移地址部分使用 32 位的值来指定,因此段内地址可以是0到4G。即一个段的最大长度可达 4G。程序中由16位的段和32位的偏移构成的48位地址或长指针称为一个逻辑地址(虚拟地址)。它唯一确定了一个数据对象的段地址和段内偏移地址。而仅由32位偏移地址或指针指定的地址是基于当前段的对象地址。

  • 80X86为段部分提供了6个存放段选择符的段寄存器:CS、DS、ES、SS、FS和GS。其中CS总是用于寻址代码段,而堆栈段则专门使用 SS段寄存器。在任何指定时刻由CS寻址的段称为当前代码段。此时 EIP寄存器中包含了当前代码段内下一条要执行指令的段内偏移地址。因此要执行指令的地址可表示成 CS:[EIP]。后面将说明的段间控制转移指令可以被用来为CS 和EIP 赋予新值,从而可以把执行位置改变到其他的代码段中,这样就实现了在不同段中程序的控制传递。

  • 由段寄存器 SS寻址的段称为当前堆栈段。栈顶由ESP寄存器内容指定。因此堆栈顶处地址是SS:[ESP]。另外4个段寄存器是通用段寄存器。当指令中没有指定所操作数据的段时,那么 DS将是默认的数据段寄存器。

  • 为了指定内存操作数的段内偏移地址,80X86指令规定了计算偏移量的很多方式,称为指令寻址方式。指令的偏移量由三部分相加组成:基地址寄存器、变址寄存器和一个偏移常量。即:

偏移地址 = 基地址 +(变址 x 比例因子)+ 偏移量

4.2.2 地址变换

  • 任何完整的内存管理系统都包含两个关键部分:保护和地址变换。提供保护措施是可以防止一个任务访问另一个任务或操作系统的内存区域。地址变换能够让操作系统在给任务分配内存时具有灵活性,并且因为我们可以让某些物理地址不被任何逻辑地址所映射,所以在地址变换过程中同时也提供了内存保护功能。

  • 正如上面提到的,计算机中的物理内存是字节的线性数组,每个字节具有一个唯一的物理地址;程序中的地址是由两部分构成的逻辑地制。这种逻辑地址并不能直接用于访问物理内存,而需要使用地址变换机制将它变换或映射到物理内存地址上。内存管理机制即用于将这种逻辑地址转换成物理内存地址。

  • 为了减少确定地址变换所需要的信息,变换或映射通常以内存块作为操作单位。分段机制和分页机制是两种广泛使用的地址变换技术。它们的不同之处在于逻辑地址是如何组织成被映射的内存块、变换信息如何指定以及编程人员如何进行操作。分段和分页操作都使用驻留在内存中的表来指定它们各自的变换信息。这些表只能由操作系统访问,以防止应用程序擅自修改。

  • 80X86在从逻辑地址到物理地址变换过程中使用了分段和分页两种机制,见图4-4所示。第一阶段使用分段机制把程序的逻辑地址变换成处理器可寻址内存空间(称为线性地址空间)中的地址。第二阶段使用分页机制把线性地址转换为物理地址。在地址变换过程中,第一阶段的分段变换机制总是使用的,而第二阶段的分页机制则是供选用的。如果没有启用分页机制,那么分段机制产生的线性地址空间就直接映射到处理器的物理地址空间上。物理地址空间定义为处理器在其地址总线上能够产生的地址范围。

4-7.png

  1. 分段机制

  2. 分段提供了隔绝各个代码、数据和堆栈区域的机制,因此多个程序(或任务)可以运行在同一个处理器上而不会互相干扰。分页机制为传统需求页、虚拟内存系统提供了实现机制。其中虚拟内存系统用于实现程序代码按要求被映射到物理内存中。分页机制当然也能用于提供多任务之间的隔离措施。

  3. 如图4-5所示,分段提供了一种机制,用于把处理器可寻址的线性地址空间划分成一些较小的称为段的受保护地址空间区域。段可以用来存放程序的代码、数据和堆栈,或者用来存放系统数据结构(例如TSS 或LDT)。如果处理器中有多个程序或任务在运行,那么每个程序可分配各自的一套段。此时处理器就可以加强这些段之间的界限,并且确保一个程序不会通过访问另一个程序的段而干扰程序的执行。分段机制还允许对段进行分类。这样,对特定类型段的操作能够受到限制。

  4. 一个系统中所有使用的段都包含在处理器线性地址空间中。为了定位指定段中的一个字节,程序必须提供一个逻辑地址。逻辑地址包括一个段选择符和一个偏移量。段选择符是一个段的唯一标识。另外,段选择符提供了段描述符表(例如全局描述符表GDT)中一个数据结构(称为段描述符)的偏移量。每个段都有一个段描述符。段描述符指明段的大小、访问权限和段的特权级、段类型以及段的第1个字节在线性地址空间中的位置(称为段的基地址)。逻辑地址的偏移量部分加到段的基地址上就可以定位段中某个字节的位置。因此基地址加上偏移量就形成了处理器线性地址空间中的地址。

  5. 线性地址空间与物理地址空间具有相同的结构。相对于两维的逻辑地址空间来说,它们两者都是一维地址空间。虚拟地址(逻辑地址)空间可包含最多16K的段,而每个段最长可达4GB,使得虚拟地址空间容量达到64TB(2^46)。线性地址空间和物理地址空间都是 4GB(2^32)。实际上,如果禁用分页机制,那么线性地址空间就是物理地址空间。

4-8.png

  1. 分页机制

  2. 因为多任务系统通常定义的线性地址空间都要比其含有的物理内存容量大得多,所以需要使用某种“虚拟化”线性地址空间的方法,即使用虚拟存储技术。虚拟存储是一种内存管理技术,使用这种技术可让编程人员产生内存空间要比计算机中实际物理内存容量大很多的错觉。利用这种错觉,我们可以随意编制大型程序而无需考虑实际物理内存究竟有多少。

  3. 分页机制支持虚拟存储技术。在使用虚拟存储的环境中,大容量的线性地址空间需要使用小块的物理内存(RAM或ROM)以及某些外部存储空间(例如大容量硬盘)来模拟。当使用分页时,每个段被划分成页面(通常每页为4KB大小),页面会被存储于物理内存中或硬盘上。操作系统通过维护一个页目录和一些页表来留意这些页面。当程序(或任务)试图访问线性地址空间中的一个地址位置时,处理器就会使用页目录和页表把线性地址转换成一个物理地址,然后在该内存位置上执行所要求的操作(读或写)。

  4. 如果当前被访问的页面不在物理内存中,处理器就会中断程序的执行(通过产生一个页错误异常)。然后操作系统就可以从硬盘上把该页面读入物理内存中,并继续执行刚才被中断的程序。当操作系统严格实现了分页机制时,那么对于正确执行的程序来说页面在物理内存和硬盘之间的交换就是透明的。

  5. 80X86分页机制最适合支持虚拟存储技术。分页机制会使用大小固定的内存块,而分段管理则使用了大小可变的块来管理内存。无论在物理内存中还是在硬盘上,分页使用固定大小的块更为适合管理物理内存。另一方面,分段机制使用大小可变的块更适合处理复杂系统的逻辑分区。可以定义与逻辑块大小适合的内存单元而无需受到固定大小页面的限制。每个段都可以作为一个单元来处理,从而简化了段的保护和共享操作。

  6. 分段和分页是两种不同的地址变换机制,它们都对整个地址变换操作提供独立的处理阶段。尽管两种机制都使用存储在内存中的变换表,但所用的表结构不同。实际上,段表存储在线性地址空间,而页表则保存在物理地址空间。因而段变换表可由分页机制重新定位而无需段机制的信息或合作。段变换机制把虚拟地址(逻辑地址)变换成线性地址,并且在线性地址空间中访问自己的表,但是并不知晓分页机制把这些线性地址转换到物理地址的过程。类似地,分页机制也不知道程序产生地址的虚拟地址空间。分页机制只是简单地把线性地址转换成物理地址,并且在物理内存中访问自己的转换表。

4.2.3 保护

  • 80X86支持两类保护。其一是通过给每个任务不同的虚拟地址(逻辑地址)空间来完全隔离各个任务。这是通过给每个任务逻辑地址到物理地址不同的变换映射来做到。另一个保护机制对任务进行操作,以保护操作系统内存段和处理器特殊系统寄存器不被应用程序访问。

  • 任务之间的保护

  • 保护的一个重要方面是提供应用程序各任务之间的保护能力。80X86使用的方法是通过把每个任务放置在不同的虚拟地址空间中,并给予每个任务不同的逻辑地址到物理地址的变换映射。每个任务中的地址变换功能被定义成一个任务中的逻辑地址映射到物理内存的一部分区域,而另一个任务中的逻辑地址映射到物理内存中的不同区域中。这样,因为一个任务不可能生成能够映射到其他任务逻辑地址对应使用的物理内存部分,所以所有任务都被隔绝开了。只需给每个任务各自独立的映射表,每个任务就会有不同的地址变换函数。在 80X86中,每个任务都有自己的段表和页表。当处理器切换去执行一个新任务时,任务切换的关键部分就是切换到新任务的变换表。

  • 通过在所有任务中安排具有相同的虚拟到物理地址映射部分,并且把操作系统存储在这个公共的虚拟地址空间部分,操作系统可以被所有任务共享。这个所有任务都具有的相同虚拟地址空间部分被称为全局地址空间(Global address space)。这也正是现代Linux 操作系统使用虚拟地址空间的方式。

  • 每个任务唯一的虚拟地址空间部分被称为局部地址空间(Local address space)。局部地址空间含有需要与系统中其他任务区别开的私有的代码和数据。由于每个任务中具有不同的局部地址空间,因此两个不同任务中对相同虚拟地址处的引用将转换到不同的物理地址处。这使得操作系统可以给与每个任务的内存相同的虚拟地址,但仍然能隔绝每个任务。另一方面,所有任务在全局地址空间中对相同虚拟地址的引用将被转换到同一个物理地址处。这给公共代码和数据(例如操作系统)的共享提供了支持。

  • 特权级保护

  • 在一个任务中,定义了4个执行特权级(Privilege Levels),用于依据段中含有数据的敏感度以及任务中不同程序部分的受信程度,来限制对任务中各段的访问。最敏感的数据被赋予了最高特权级,它们只能被任务中最受信任的部分访问。不太敏感的数据被赋予较低的特权级,它们可以被任务中较低特权级的代码访问。

  • 特权级用数字0到3表示,0具有最高特权级,而3则是最低特权级。每个内存段都与一个特权级相关联。这个特权级限制具有足够特权级的程序来访问一个段。我们知道,处理器从CS寄存器指定的段中取得和执行指令,当前特权级(Current Privilege Level),即CPL 就是当前活动代码段的特权级,并且它定义了当前所执行程序的特权级别。CPL确定了哪些段能够被程序访问。

  • 每当程序企图访问一个段时,当前特权级就会与段的特权级进行比较,以确定是否有访问许可。在给定CPL 级别上执行的程序允许访问同级别或低级别的数据段。任何对高级别段的引用都是非法的,并且会引发一个异常来通知操作系统。

  • 每个特权级都有自己的程序栈,以避免使用共享栈带来的保护问题。当程序从一个特权级切换到另一个特权级上执行时,堆栈段也随之改换到新级别的堆栈中。

4.3 分段机制

  • 分段机制可用于实现多种系统设计。这些设计范围从使用分段机制的最小功能来保护程序的平坦模型,到使用分段机制创建一个可同时可靠地运行多个程序(或任务)的具有稳固操作环境的多段模型。

  • 多段模型能够利用分段机制全部功能提供由硬件增强的代码、数据结构、程序和任务的保护措施。通常,每个程序(或任务)都使用自己的段描述符表以及自己的段。对程序来说段能够完全是私有的,或者是程序之间共享的。对所有段以及系统上运行程序各自执行环境的访问都由硬件控制。

  • 访问检查不仅能够用来保护对段界限以外地址的引用,而且也能用来在某些段中防止执行不允许的操作。例如,因为代码段被设计成是只读形式的段,因此可以用硬件来防止对代码段执行写操作。段中的访问权限信息也可以用来设置保护环或级别。保护级别可用于保护操作系统程序不受应用程序非法访问。

4.3.1 段的定义

  • 80386虚拟地址空间中的虚拟地址(逻辑地址)由一个段部分和一个偏移部分构成。段是虚拟地址到线性地址转换机制的基础。每个段由三个参数定义:

  • 段基地址(Base address),指定段在线性地址空间中的开始地址。基地址是线性地址,对应于段中偏移0处。

  • 段限长(limit),是虚拟地址空间中段内最大可用偏移位置。它定义了段的长度。

  • 段属性(Attributes),指定段的特性。例如该段是否可读、可写或可作为一个程序执行;段的特权 级等。

  • 段限长定义了在虚拟地址空间中段的大小。段基址和段限长定义了段所映射的线性地址范围或区域。段内0到limit 的地址范围对应线性地址中范围 base 到base + limit。偏移量大于段限长的虚拟地址是无意义的,如果使用则会导致异常。另外,若访问一个段并没有得到段属性许可则也会导致异常。例如,如果你试图写一个只读的段,那么80386就会产生一个异常。另外,多个段映射到线性地址中的范围可以部分重叠或覆盖,甚至完全重叠,见图 4-6 所示。在本书介绍的Linux 0.1x 系统中,一个任务的代码段和数据段的段限长相同,并被映射到线性地址完全相同而重叠的区域上。

4-9.png

  • 段的基地址、段限长以及段的保护属性存储在一个称为段描述符(SegmentDescriptor)的结构项中。在逻辑地址到线性地址的转换映射过程中会使用这个段描述符。段描述符保存在内存中的段描述符表(Descriptor table)中。段描述符表是包含段描述符项的一个简单数组。

  • 即使利用段的最小功能,使用逻辑地址也能访问处理器地址空间中的每个字节。逻辑地址由16位的段选择符和 32 位的偏移量组成,见图 4-7所示。段选择符指定字节所在的段,而偏移量指定该字节在段中相对于段基地址的位置。

  • 为了把逻辑地址转换成一个线性地址,处理器会执行以下操作:

  • 使用段选择符中的偏移值(段索引)在GDT或LDT表中定位相应的段描述符。(仅当一个新的段 选择符加载到段寄存器中时才需要这一步。)

  • 利用段描述符检验段的访问权限和范围,以确保该段是可访问的并且偏移量位于段界限内。

  • 把段描述符中取得的段基地址加到偏移量上,最后形成一个线性地址。

4-10.png

  • 如果没有开启分页,那么处理器直接把线性地址映射到物理地址(即线性地址被送到处理器地址总线上)。如果对线性地址空间进行了分页处理,那么就会使用二级地址转换把线性地址转换成物理地址。

4.3.2 段描述符表

  • 段描述符表是段描述符的一个数组,见图 4-8 所示。描述符表的长度可变,最多可以包含 8192个8字节描述符。有两种描述符表:全局描述符表 GDT(Global descriptor table);局部描述符表LDT(Local descriptor table)。

4-11.png

  • 描述符表存储在由操作系统维护着的特殊数据结构中,并且由处理器的内存管理硬件来引用。这些特殊结构应该保存在仅由操作系统软件访问的受保护的内存区域中,以防止应用程序修改其中的地址转换信息。虚拟地址空间被分割成大小相等的两半。一半由GDT来映射变换到线性地址,另一半则由LDT来映射。整个虚拟地址空间共含有 2^14个段:一半空间(即2^13个段)是由GDT 映射的全局虚拟地址空间,另一半是由LDT映射的局部虚拟地址空间。通过指定一个描述符表(GDT或LDT)以及表中描述符号,我们就可以定位一个描述符。

  • 当发生任务切换时,LDT会更换成新任务的LDT,但是GDT并不会改变。因此,GDT所映射的一半虚拟地址空间是系统中所有任务共有的,但是LDT所映射的另一半则在任务切换时被改变。系统中所有任务共享的段由GDT来映射。这样的段通常包括含有操作系统的段以及所有任务各自的包含LDT的特殊段。LDT段可以想象成属于操作系统的数据。

  • 图4-9示出一个任务中的段如何能在GDT和LDT之间分开。图中共有6个段,分别用于两个应用程序(A和B)以及操作系统。系统中每个应用程序对应一个任务,并且每个任务有自己的LDT。应用程序 A在任务 A 中运行,拥有 LDT_A,用来映射段 Code_A和 Data_A。类似地,应用程序B 在任务B 中运行,使用 LDT_B来映射 Code_B和 Data_B 段。包含操作系统内核的两个段 Code_os 和 Data_os 使用 GDT 来映射,这样它们可以被两个任务所共享。两个LDT段:LDT_A和LDT_B也使用GDT来映射。

4-12.png

  • 当任务 A 在运行时,可访问的段包括 LDT A 映射的 Code A 和 Data A段,加上 GDT 映射的操作系统的段 Code os 和 Data os。当任务B 在运行时,可访问的段包括 LDTB 映射的 Code B 和 Data B 段,加上 GDT 映射的段。

  • 这个例子通过让每个任务使用不同的LDT,演示了虚拟地址空间如何能够被组织成隔离每个任务。当任务A在运行时,任务B 的段不是虚拟地址空间的部分,因此任务A没有办法访问任务B的内存。同样地,当任务B运行时,任务A的段也不能被寻址。这种使用LDT来隔离每个应用程序任务的方法,正是关键保护需求之一。

  • 每个系统必须定义一个GDT,并可用于系统中所有程序或任务。

  • GDT本身并不是一个段,而是线性地址空间中的一个数据结构。GDT的基线性地址和长度值必须加载进GDTR寄存器中。GDT的基地址应该进行内存8字节对齐,以得到最佳处理器性能。GDT 的限长以字节为单位。与段类似,限长值加上基地址可得到最后表中最后一个字节的有效地址。限长为0表示有1 个有效字节。因为段描述符总是8字节长,因此GDT的限长值应该设置成总是8的倍数减1(即8N-1)。

  • 处理器并不使用GDT中的第1个描述符。把这个“空描述符”的段选择符加载进一个数据段寄存器(DS、ES、FS或GS)并不会产生一个异常,但是若使用这些加载了空描述符的段选择符访问内存时就肯定会产生一般保护性异常。通过使用这个段选择符初始化段寄存器,那么意外引用未使用的段寄存器肯定会产生一个异常。

  • LDT表存放在 LDT类型的系统段中。此时 GDT 必须含有LDT 的段描述符。如果系统支持多LDT 的话,那么每个LDT都必须在GDT中有一个段描述符和段选择符。一个LDT的段描述符可以存放在GDT 表的任何地方。

  • 访问LDT需使用其段选择符。为了在访问LDT时减少地址转换次数,LDT的段选择符、基地址、段限长以及访问权限需要存放在LDTR寄存器中。

  • 当保存GDTR寄存器内容时(使用SGDT指令),一个48位的“伪描述符”被存储在内存中。为了在 用户模式(特权级3)避免对齐检查出错,伪描述符应该存放在一个奇字地址处(即地址 MOD 4 = 2)。这会让处理器先存放一个对齐的字,随后是一个对齐的双字(4字节对齐处)。用户模式程序通常不会保存伪描述符,但是可以通过使用这种对齐方式来避免产生一个对齐检查出错的可能性。当使用 SIDT指令保存IDTR 寄存器内容时也需要使用同样的对齐方式。然而,当保存LDTR 或任务寄存器(分别使用 SLTR 或 STR指令)时,伪描述符应该存放在双字对齐的地址处(即地址 MOD 4 = 0)。

4.3.3 段选择符

  • 段选择符(或称段选择子)是段的一个16 位标识符,见图 4-10 所示。段选择符并不直接指向段,而是指向段描述符表中定义段的段描述符。段选择符3个字段内容:

  • 请求特权级 RPL(Requested Privilege Level);

  • 表指示标志 TI(Table Index);

  • 索引值(Index)

4-13.png

  • 例如,图 4-11(a)中选择符(0x08)指定了 GDT 中具有 RPL=0 的段 1,其索引字段值是 1,TI位是 0, 指定GDT表。

  • 图4-11(b)中选择符(0x10)指定了GDT中具有RPL=0 的段 2,其索引字段值是 2,TI 位是 0,指定 GDT 表。

  • 图 4-11(c)中选择符(0x0f)指定了LDT 中具有RPL=3 的段 1,其索引字段值是 1, TI位是 1,指定LDT表。

  • 图 4-11(d)中选择符(0x17)指定了LDT中具有RPL=3 的段2,其索引字段值是 2,TI位是1,指定 LDT 表。实际上,图 4-11 中的前 4 个选择符:(a)、(b)、(c)和(d)分别就是Linux 0.1x 内核的内核代码段、内核数据段、任务代码段和任务数据段的选择符。

  • 图4-11(e)中的选择符(0xfff)指定LDT 表中RPL=3的段8191。其索引字段值是0b1111111111111(即8191),TI位等于1,指定LDT表。

4-14.png

  • 另外,处理器不使用GDT表中的第1项。指向GDT该项的选择符(即索引值为0,TI标志为0的选择符)用作为“空选择符",见图4-11(f)所示。当把空选择符加载到一个段寄存器(除了CS 和 SS以外) 中时,处理器并不产生异常。但是当使用含有空选择符的段寄存器用于访问内存时就会产生异常。当把空选择符加载到CS 或 SS 段寄存器中时将会导致一个异常。

  • 为减少地址转换时间和编程复杂性,处理器提供可存放最多6个段选择符的寄存器(见图4-12所示),即段寄存器。每个段寄存器支持特定类型的内存引用(代码、数据或堆栈)。原则上执行每个程序都起码需要把有效的段选择符加载到代码段(CS)、数据段(DS)和堆栈段(SS)寄存器中。处理器还另外提供三个辅助的数据段寄存器(ES、FS 和GS),可被用于让当前执行程序(或任务)能够访问其他几个数据段。

4-15.png

  • 对于访问某个段的程序,必须已经把段选择符加载到一个段寄存器中。

  • 为加载段寄存器,提供了两类加载指令:

  • 象MOV、POP、LDS、LES、LSS、LGS以及LFS指令。这些指令显式地直接引用段寄存器;

  • 隐式加载指令,例如使用长指针的CALL、JMP 和RET 指令、IRET、INTn、INT0 和 INT3 等指令。这些指令在操作过程中会附带改变CS寄存器(和某些其他段寄存器)的内容。

  • MOV指令当然也可以用于把段寄存器可见部分内容存储到一个通用寄存器中。

4.3.4 段描述符

前面我们已经说明了使用段选择符来定位描述符表中的一个描述符段描述符是GDT和LDT表中的一个数据结构项,用于向处理器提供有关一个段的位置和大小信息以及访问控制的状态信息。每个段描述符长度是8字节,含有三个主要字段:段基地址、段限长和段属性。段描述符通常由编译器、链接器、加载器或者操作系统来创建,但绝不是应用程序。图 4-13示出了所有类型段描述符的一般格式(图中共有八个字节,表示8字节长的段描述符结构)。

4-16.png

  • 一个段描述符中各字段和标志的含义如下:

  • 段限长字段LIMIT(Segment limit field)

段限长Limit字段用于指定段的长度。处理器会把段描述符中两个段限长字段组合成一个20位的值,并根据颗粒度标志G来指定段限长Limit值的实际含义。如果G=0,则段长度Limit 范围可从1字节到1MB 字节,单位是字节。如果G=1,则段长度Limit 范围可从 4KB 到 4GB,单位是 4KB.

根据段类型中的段扩展方向标志E,处理器以两种不同方式使用段限长Limit。对于向上扩展的段(简称上扩段),逻辑地址中的偏移值范围可以从〇到段限长值Limit。大于段限长Limit 的偏移值将产生一般保护性异常。对于向下扩展的段(简称下扩段),段限长Limit的含义相反。根据默认栈指针大小标志B 的设置,偏移值范围可从段限长Limit 到0xFFFFFFFF 或 0xFFFF。而小于段限长Limit 的偏移值将产生一般保护性异常。对于下扩段,减小段限长字段中的值会在该段地址空间底部分配新的内存,而不是在顶部分配。80X86的栈总是向下扩展的,因此这种实现方式很适合扩展堆栈。

  1. 基地址字段BASE(Base address field)

该字段定义在 4GB 线性地址空间中一个段字节0所处的位置。处理器会把3个分立的基地址字段组合形成一个 32位的值。段基地址应该对齐16字节边界。虽然这不是要求的,但通过把程序的代码和数据段对齐在16字节边界上,可以让程序具有最佳性能。

  1. 段类型字段 TYPE(Type field)

类型字段指定段或门(Gate)的类型、说明段的访问种类以及段的扩展方向。该字段的解释依赖于描述符类型标志S指明是一个应用(代码或数据)描述符还是一个系统描述符。TYPE 字段的编码对代码、数据或系统描述符都不同,见图 4-14所示。

  1. 描述符类型标志 S(Descriptor type flag)

描述符类型标志S指明一个段描述符是系统段描述符(当S=0)还是代码或数据段描述符(当S=1)。

  1. 描述符特权级字段 DPL(Descriptor privilege level)

DPL 字段指明描述符的特权级。特权级范围从0到3。0 级特权级最高,3 级最低。DPL 用于控制对段的访问。

  1. 段存在标志 P(Segment present)

段存在标志P指出一个段是在内存中(P=1)还是不在内存中(P=0)。当一个段描述符的 P 标志为时,那么把指向这个段描述符的选择符加载进段寄存器将导致产生一个段不存在异常。内存管理软件可以使用这个标志来控制在某一给定时间实际需要把那个段加载进内存中。这个功能为虚拟存储提供了除分页机制以外的控制。图 4-15 给出了当P=0时的段描述符格式。当P标志为0 时,操作系统可以自由使用格式中标注为可用(Avaliable)的字段位置来保存自己的数据,例如有关不存在段实际在什么地方的信息。

  1. D/B(默认操作大小/默认栈指针大小和/或上界限)标志( Default operation size/default stack pointer size and/or upper bound)

根据段描述符描述的是一个可执行代码段、下扩数据段还是一个堆栈段,这个标志具有不同的功能。(对于32位代码和数据段,这个标志应该总是设置为1;对于16位代码和数据段,这个标志被设置为0。)

可执行代码段。此时这个标志称为D标志并用于指出该段中的指令引用有效地址和操作数的默认长度。如果该标志置位,则默认值是32 位地址和32位或8位的操作数;如果该标志为 0,则默认值是16位地址和16位或8位的操作数。指令前缀0x66可以用来选择非默认值的操作数大小;前缀0x67可用来选择非默认值的地址大小。

栈段(由 SS 寄存器指向的数据段)。此时该标志称为B(Big)标志,用于指明隐含堆栈操作(例如PUSH、POP或CALL)时的栈指针大小。如果该标志置位,则使用 32位栈指针并存放在ESP寄存器中;如果该标志为0,则使用 16位栈指针并存放在SP寄存器中。如果堆栈段被设置成一个下扩数据段,这个B标志也同时指定了堆栈段的上界限。

下扩数据段。此时该标志称为B标志,用于指明堆栈段的上界限。如果设置了该标志,则堆栈段的上界限是0xFFFFFFFF(4GB);如果没有设置该标志,则堆栈段的上界限是 0xFFFF(64KB)。

  1. 颗粒度标志G(Granularity)

该字段用于确定段限长字段Limit 值的单位。如果颗粒度标志为0,则段限长值的单位是字节; 如果设置了颗粒度标志,则段限长值使用 4KB单位。(这个标志不影响段基地址的颗粒度,基地址的颗粒度总是字节单位。)若设置了G标志,那么当使用段限长来检查偏移值时,并不会去检查偏移值的12位最低有效位。例如,当G=1时,段限长为0表明有效偏移值为0到4095。

  1. 可用和保留比特位(Available and reserved bits)

段描述符第 2个双字的位 20可供系统软件使用;位21是保留位并应该总是设置为0。

4-17.png

4-18.png

4-19.png

4.3.5 代码和数据段描述符类型

  • 当段描述符中S(描述符类型)标志被置位,则该描述符用于代码或数据段。此时类型字段中最高比特位(第2个双字的位11)用于确定是数据段的描述符(复位)还是代码段的描述符(置位)。

  • 对于数据段的描述符,类型字段的低 3位(位 8、9、10)被分别用于表示已访问A(Accessed)、可写W(Write-enable)和扩展方向E(Expansion-direction),参见表 4-3 中有关代码和数据段类型字段比特位的说明。根据可写比特位W的设置,一个数据段可以是只读的,也可以是可读可写的。

4-20.png

  • 堆栈段必须是可读/写的数据段。若使用不可写数据段的选择符加载到SS 寄存器中,将导致一个一般保护异常。如果堆栈段的长度需要动态地改变,那么堆栈段可以是一个向下扩展的数据段(扩展方向标志置位)。这里,动态改变段限长将导致栈空间被添加到栈底部。

  • 已访问比特位指明自从上次操作系统复位该位之后一个段是否被访问过。每当处理器把一个段的段选择符加载进段寄存器,它就会设置该位。该位需要明确地清除,否则一直保持置位状态。该位可用于虚拟内存管理和调试。

  • 对于代码段,类型字段的低 3位被解释成已访问A(Accessed)、可读R(Read-enable)和一致的C(Conforming)。根据可读R标志的设置,代码段可以是只能执行、可执行/可读。当常数或其他静态数据以及指令码被放在了一个ROM中时就可以使用一个可执行/可读代码段。这里,通过使用带CS 前缀的指令或者把代码段选择符加载进一个数据段寄存器(DS、ES、FS或GS),我们可以读取代码段中的数据。在保护模式下,代码段是不可写的。

  • 代码段可以是一致性的或非一致性的。向更高特权级一致性代码段的执行控制转移,允许程序以当前特权级继续执行。向一个不同特权级的非一致性代码段的转移将导致一般保护异常,除非使用了一个调用门或任务门(有关一致性和非一致性代码段的详细信息请参见“直接调用或跳转到代码段”)。不访问保护设施的系统工具以及某些异常类型(例如除出错、溢出)的处理过程可以存放在一致性代码段中。需要防止低特权级程序或过程访问的工具应该存放在非一致性代码段中。

  • 所有数据段都是非一致性的,即意味着它们不能被低特权级的程序或过程访问。然而,与代码段不同,数据段可以被更高特权级的程序或过程访问,而无须使用特殊的访问门。

  • 如果GDT或LDT中一个段描述符被存放在ROM中,那么若软件或处理器试图更新(写)在ROM 中的段描述符时,处理器就会进入一个无限循环。为了防止这个问题,需要存放在ROM中的所有描述符的已访问位应该预先设置成置位状态。同时,删除操作系统中任何试图修改ROM中段描述符的代码。

4.3.6 系统描述符类型

  • 当段描述符中的S标志(描述符类型)是复位状态(0)的话,那么该描述符是一个系统描述符。处理器能够识别以下一些类型的系统段描述符:

  • 局部描述符表(LDT)的段描述符;

  • 任务状态段(TSS)描述符;

  • 调用门描述符;

  • 中断门描述符;

  • 陷阱门描述符;

  • 任务门描述符。

  • 这些描述符类型可分为两大类:系统段描述符和门描述符。系统段描述符指向系统段(如LDT和TSS 段),门描述符就是一个“门”,对于调用、中断或陷阱门,其中含有代码段的选择符和段中程序入口点的指针;对于任务门,其中含有TSS 的段选择符。表4-4给出了系统段描述符和门描述符类型字段的编码。

4-21.png

4.4 分页机制

前面介绍的分段机制在各种可变长度的内存区域上操作。与分段机制不同,分页机制对固定大小的内存块(称为页面)进行操作。分页机制把线性和物理地址空间都划分成页面。线性地址空间中的任何页面可以被映射到物理地址空间的任何页面上。图 4-16示出了分页机制是如何把线性和物理地址空间都划分成各个页面,并在这两个空间之间提供了任意映射。图中的箭头把线性地址空间中的页面与物理地址空间中的页面对应了起来。

4-22.png

  • 80X86 使用 4K(2^12)字节固定大小的页面。每个页面均是 4KB,并且对齐于 4K 地址边界处。这表示分页机制把 232字节(4GB)的线性地址空间划分成2^20(1M=1048576)个页面。分页机制通过把线性地址空间中的页面重新定位到物理地址空间中进行操作。由于 4K大小的页面作为一个单元进行映射,并且对齐于 4K边界,因此线性地址的低 12 比特位可作为页内偏移量直接作为物理地址的低 12 位。分页机制执行的重定位功能可以看作是把线性地址的高 20 位转换到对应物理地址的高 20 位。

  • 分页与分段最大的不同之处在于分页使用了固定长度的页面。段的长度通常与存放在其中的代码或数据结构具有相同的长度。与段不同,页面有固定的长度。如果仅使用分段地址转换,那么存储在物理内存中的一个数据结构将包含其所有的部分。但如果使用了分页,那么一个数据结构就可以一部分存储于物理内存中,而另一部分保存在磁盘中。

  • 正如前述,为了减少地址转换所要求的总线周期数量,最近访问的页目录和页表会被存放在处理器的缓冲器件中,该缓冲器件被称为转换查找缓冲区 TLB(TranslationLookaside Buffer)。TLB 可以满足大多数读页目录和页表的请求而无需使用总线周期。只有当TLB中不包含要求的页表项时才会使用额外的总线周期从内存中读取页表项,这通常在一个页表项很长时间没有访问过时才会出现这种情况。

4.4.1 页表结构

  • 分页转换功能由驻留在内存中的表来描述,该表称为页表(page table),存放在物理地址空间中。页表可以看作是简单的2"物理地址数组。线性到物理地址的映射功能可以简单地看作是进行数组查找。线性地址的高 20位构成这个数组的索引值,用于选择对应页面的物理(基)地址。线性地址的低12位给出了页面中的偏移量,加上页面的基地址最终形成对应的物理地址。由于页面基地址对齐在 4K边界上,因此页面基地址的低 12 位肯定是 0。这意味着高 20 位的页面基地址和 12 位偏移量连接组合在一起就能得到对应的物理地址。

  • 页表中每个页表项大小为 32 位。由于只需要其中的 20位来存放页面的物理基地址,因此剩下的 12 位可用于存放诸如页面是否存在等的属性信息。如果线性地址索引的页表项被标注为存在的,则表示该项即有效,我们可以从中取得页面的物理地址。如果项中表明不存在,那么当访问对应物理页面时就会产生一个异常。

4.4.1.1 两级页表结构

  • 页表含有 2^20(1M)个表项,而每项占用4字节。如果作为一个表来存放的话,它们最多将占用 4MB 的内存。因此为了减少内存占用量,80X86使用了两级表。由此,高 20位线性地址到物理地址的转换也被分成两步来进行,每步使用(转换)其中10个比特。

  • 第一级表称为页目录(page directory)。它被存放在1页 4K页面中,具有 2^10(1K)个4字节长度的表项。这些表项指向对应的二级表。线性地址的最高10位(位31--22)用作一级表(页目录)中的索引值来选择2^10个二级表之一。

  • 第二级表称为页表(page table),它的长度也是1个页面,最多含有 1K个4字节的表项。每个4字节表项含有相关页面的 20位物理基地址。二级页表使用线性地址中间 10位(位 21--12)作为表项索引值,以获取含有页面 20 位物理基地址的表项。该 20 位页面物理基地址和线性地址中的低 12 位(页内偏移)组合在一起就得到了分页转换过程的输出值,即对应的最终物理地址。

  • 图4-17示出了二级表的查找过程。其中CR3寄存器指定页目录表的基地址。线性地址的高 10位用于索引这个页目录表,以获得指向相关第二级页表的指针。线性地址中间10位用于索引二级页表,以获得物理地址的高 20位。线性地址的低12位直接作为物理地址低 12位,从而组成一个完整的32位物理地址。

4-23.png

4.4.1.2 不存在的页表

  • 通过使用二级表结构,我们还没有解决需要使用4MB内存来存放页表的问题。二级表结构允许页表被分散在内存各个页面中,而不需要保存在连续的 4MB内存块中。另外,并不需要为不存在的或线性地址空间未使用部分分配二级页表。虽然目录表页面必须总是存在于物理内存中,但是二级页表可以在需要时再分配。这使得页表结构的大小对应于实际使用的线性地址空间大小。

  • 页目录表中每个表项也有一个存在(present)属性,类似于页表中的表项。页目录表项中的存在属性指明对应的二级页表是否存在。

4.4.2 页表项格式

4-24.png

  • P--位0 是存在(Present)标志,用于指明表项对地址转换是否有效。P=1 表示有效;P=0 表示无效。在页转换过程中,如果说涉及的页目录或页表的表项无效,则会导致一个异常。如果P=0,那么除表示表项无效外,其余比特位可供程序自由使用,见图 4-18(b)所示。例如,操作系统可以使用这些位来保存已存储在磁盘上的页面的序号。

  • R/W--位1 是读/写(Read/Write)标志。如果等于1,表示页面可以被读、写或执行。如果为0,表示页面只读或可执行。当处理器运行在超级用户特权级(级别0、1或 2)时,则R/W 位不起作用。页目录项中的R/W位对其所映射的所有页面起作用。

  • U/S--位 2 是用户/超级用户(User/Supervisor)标志。如果为1,那么运行在任何特权级上的程序都可以访问该页面。如果为0,那么页面只能被运行在超级用户特权级(0、1或 2)上的程序访问。页目录项中的U/S位对其所映射的所有页面起作用。

  • A--位5是已访问(Accessed)标志。当处理器访问页表项映射的页面时,页表表项的这个标志就会被置为1。当处理器访问页目录表项映射的任何页面时,页目录表项的这个标志就会被置为1。处理器只负责设置该标志,操作系统可通过定期地复位该标志来统计页面的使用情况。

  • D--位6是页面已被修改(Dirty)标志。当处理器对一个页面执行写操作时,就会设置对应页表表项的D标志。处理器并不会修改页目录项中的D标志。

  • AVL--该字段保留专供程序使用。处理器不会修改这几位,以后的升级处理器也不会。

4.4.3 虚拟存储

4.5~4.9 暂略需要时再看

4.7 任务管理

略,待补充

4.7.1 任务的结构和状态

  • 一个任务由两部分构成: 任务执行空间和任务状态段TSS(Task-state segment)任务执行空间包括代码段、堆栈段和一个或多个数据段,见图4-33所示。如果操作系统使用了处理器的特权级保护机制,那么任务执行空间就需要为每个特权级提供一个独立的堆栈空间。TSS指定了构成任务执行空间的各个段,并且为任务状态信息提供存储空间。在多任务环境中,TSS也为任务之间的链接提供了处理方法。

4-33.png

  • 一个任务使用指向其 TSS 的段选择符来指定。当一个任务被加载进处理器中执行时,那么该任务的段选择符、基地址、段限长以及 TSS 段描述符属性就会被加载进任务寄存器 TR(Task Register)中。如果使用了分页机制,那么任务使用的页目录表基地址就会被加载进控制寄存器CR3中。当前执行任务的状态由处理器中的以下所有内容组成:

  • 所有通用寄存器和段寄存器信息;

  • 标志寄存器EFLAGS、程序指针EIP、控制寄存器CR3、任务寄存器和LDTR寄存器;

  • 段寄存器指定的任务当前执行空间;

  • I/O 映射位图基地址和I/O位图信息(在TSS中);

  • 特权级0、1和 2 的堆栈指针(在TSS 中);

  • 链接至前一个任务的链指针(在TSS中)。

4.7.2 任务的执行

略,待补充

4.8 保护模式编程初始化

4.9 一个简单地多任务内核实例

4.9.1 多任务程序结构和工作原理

4.9.2 引导启动程序boot.s

4.9.3 多任务内核程序head.s

2024.12.25-12.28 于昆明