保护模式内存管理

2. 保护模式内存管理

2.1 内存管理概览

​ IA-32架构的内存管理机制主要分为两部分:段式页式分段提供了一种隔绝各个代码、数据和堆栈区域的机制,以致多个程序(或任务)可以运行在同一个处理器上,但不会相互干扰。 分页则为传统需求页的虚拟内存系统提供了一种实现机制,在虚拟内存系统中,程序运行所需的环境的各个部分按需映射到物理内存中。 分页同样可以为多个任务之间提供隔离措施。 当在保护模式下运行时,必须采用某种形式的分段机制。 这里没有模式位以设置禁用分段机制,也就说在保护模式的内存管理中分段机制必需的。 但是,分页机制可选的。
​ 这两种机制(分段和分页)是可以被配置以致支持使用共享内存的简单的单程序(或单任务)系统、多任务系统或多处理器系统。
​ 如图1所示,段式管理提供了一种机制:其将处理器的可寻址内存空间(也被称为线性地址空间)划分为较小的受保护的地址空间区域,称为。 段可以用来保存程序的代码、数据和堆栈,也可用于保存系统数据结构(例如TSS或LDT)。 如果一个处理器中有多个程序(或任务)正在运行,那么每个程序都会被分配自己的一组段空间。 然后处理器可以加强这些段之间的界限,并且确保一个程序不会通过访问写入另一个程序的段而干扰另一个程序的运行。分段机制还允许对段进行分类,这样一来便可限制对特定类型段所要执行的操作。

图1 分段与分页

​ 一个系统中的所有段都包含在处理器的线性地址空间中。 为了定位到指定段中的一个字节,程序必须提供一个逻辑地址(也称为远指针)。 逻辑地址由一个段选择子和一个偏移量组成。 段选择子是一个段的唯一标识。 此外,段选择子还提供了段描述符表(如全局描述符表,GDT)中到一个被称为段描述符的数据结构的偏移量。 每个段都有一个段描述符,它指定了段的大小、段的访问权限和特权级别、段的类型以及段的第一个字节在线性地址空间中的位置(即段基址)。 将逻辑地址的偏移量部分与段的基址相加,就可以定位段内的某个字节。 因此基址加上偏移量就形成了在处理器的线性地址空间中的一个线性地址
​ 如果不启用分页,那么处理器的线性地址空间会被直接映射到处理器的物理地址空间。 物理地址空间定义为处理器可以在其地址总线上生成的地址范围。
​ 因为多任务计算系统定义的线性地址空间通常都要比其含有的物理内存容量大得多,所以需要采取一些“虚拟化”线性地址空间的方法。而这种线性地址空间的虚拟化是通过处理器的分页机制来处理的。
​ 分页机制支持建立一个虚拟内存环境,在这个环境中,可以用小量的物理内存(RAM和ROM)和一些磁盘存储空间就可以模拟大容量的线性地址空间。启用分页机制时,每个段被划为分为页(通常每个页大小为4KB),这些页存储在物理内存或硬盘中。 操作系统或程序通过维护一个页目录和一组页表来跟踪这些页。当程序(或任务)视图访问线性地址空间中的一个地址位置时,处理器便会使用页目录和页表将线性地址转换为一个物理地址,然后在该内存位置上执行所要求的操作(读或写)。
​ 如果当前被访问的页面不在物理内存中,处理器便会中断当前程序的执行(通过产生一个页错误异常)。 然后,操作系统执行程序将从磁盘读取该页面进入物理内存,然后继续执行刚才被中断的程序。
​ 当分页机制在操作系统或执行程序正常运作时,对于正确执行的程序来说,物理内存和磁盘之间的页面交换是透明的。 即使在IA-32处理器中,16位的程序也可在虚拟8086模式下运行时进行分页(透明地)。

​ 总结:

  • 逻辑地址(Logical Address):一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择子(段选择子就是段标识符)。
  • 线性地址(Linear Address):一个段的基址加上段内偏移量,形成线性地址空间中的一个线性地址。线性地址是逻辑地址到物理地址变换之间的中间层。这里段内偏移量就是逻辑地址中的段内偏移量。而段的基址跟逻辑地址中段标识符有关,通过段标识符(段选择子)可获得段描述符表中与该段选择子相关联的段描述符,段描述符提供了该段的基址。
  • 物理地址(Physical Address):物理内存中的地址。

2.2 分段机制

​ IA-32架构支持的分段机制可用于实现多种系统设计。这些设计范围从最小使用分段来保护程序的平坦模型,到使用分段来创建良好健壮性的操作环境的多段模型,在这种环境中,多个程序和任务可同时可靠运行的
​ 下面几节给出了几个例子,说明如何在系统中使用分段来提高内存管理的性能和可靠性。

2.2.1 基本平坦模型(Basic Flat Model)

​ 最简单的系统内存模型就是就是基本“平坦模型”,在这种模型中,操作系统或应用程序可访问一个连续的、未分段的地址空间。这样一来最大程度地向系统设计人员和程序员隐藏了体系机构的分段机制。
​ 若要在IA-32架构下实现内存的一个基本平坦模型,则需要至少创建两个段描述符,一个用于引用代码段,一个用于引用数据段(如图2所示)。 然而,这两个段都会被映射到整个线性地址空间:即,这两个段描述符具有相同的基址:0和相同的段限长(段的最大长度):4 GB。 通过将段限长设置为4 GB,可以避免分段机制对超过段空间大小的内存的引用产生异常,即使指定的地址不存在物理内存中。 ROM (EPROM)通常位于物理地址空间的顶部,因为处理器从FFFF_FFF0H开始执行。 RAM (DRAM)则位于在地址空间的底部,因为复位初始化后DS数据段的初始基址为0。

图2 平坦模型

2.2.2 保护平坦模型(Protected Flat Model)

​ 保护平坦模型类似于基本平坦模型,指示保护平坦模型将段限制在包含物理内存实际存在的地址范围内(如图3所示)。当尝试访问不存在的内存时,会产生一个通用保护异常(#GP)。该模型针对某些程序bug提供了最低水平的硬件保护。

图3 保护平坦模型

​ 这个保护平坦模型可以被拓展变得更加复杂,以提供更多保护功能。例如,为了确保分页机制能提供用户程序和监控程序之间代码和数据的隔离,这里需要定义四个段权限级别为3用户程序代码段和数据段、权限级别为0监管程序代码段和数据段。通常这些段都相互覆盖,并且在线性地址空间的起始地址都为0。这种平坦分段模型搭配简单的分页结构可以保护操作系统不受应用程序影响,此外通过为每个任务或进程增加单独的分页结构,其还可以确保一个应用程序不受其他应用程序影响。一些流行的多任务操作系统也使用此种类似的设计。

2.2.3 多段模型(Multi-Segment Model)

​ 多段模型(如图4所示)利用了分段机制所提供的全部功能,为代码、数据结构、程序和任务都提供了硬件级强制保护。在这个模型中,每个程序(或任务)都被分配了自己的段描述符和段空间。段可以是其分配段的程序所私有的,也可多个程序共享。此外,操作系统硬件来控制对运行在系统中的各个程序的所有段和执行环境的访问。

图4 多段模型

​ 对内存访问的检查不仅可以防止引用超过段大小的地址,还可以防止程序对某些段执行不被允许的操作。例如,一个代码为只读段,因此可使用硬件来防止对该代码段的写操作。每个段的访问权限信息也可用于设置保护区和保护级别。保护级别则可用于保护操作系统程序不被未经授权的应用程序访问。
​ 分页机制可以与图2、图3和图4中所描述的任一分段模型搭配使用。 处理器的分页机制将线性地址空间(在线性地址空间中,段也被映射)划分为页面(如图1所示)。 然后在线性地址空间中的页面会映射到物理地址空间中的页面。 分页机制提供了几种页级保护功能,它们可以与段保护功能一起使用,也可以代替段保护功能。 例如,它允许逐页执行读写保护。 分页机制还提供了两级用户-监管保护,也可以逐页指定这种保护。

2.3 逻辑地址和线性地址的转换

​ 在系统保护模式中,处理器需要经过两部分的地址转换以完成到物理地址空间的转换:逻辑地址转换和线性地址空间分页。
​ 即使最小程度地使用分段,处理器地址空间中的每一个字节都需要使用逻辑地址访问。一个逻辑地址16位段选择符32位偏移量组成,如图5所示。段选择符标识该字节所位于的段,偏移量确定字节相对于段基址的段内位置
处理器会将每个逻辑地址转换成线性地址。 线性地址是处理器线性地址空间中的一个32位地址。 与物理地址空间类似,线性地址空间是一个平坦的(未分段的)、大小为$2^{32}$字节的地址空间,地址范围从0开始到0xFFFFFFFF结束。线性地址空间包含所有的段系统定义的系统表

图5 逻辑地址到线性地址的转换

​ 从逻辑地址转换到线性地址,处理器需要做如下操作:

  1. 使用段选择子中的偏移量来定位在GDT或LDT中对应的段描述符,然后读取到处理器中。(这步仅当一个新的段选择子被加载到段寄存器时才需要)。
  2. 检查段描述符以检查段的访问权限和范围,以确保该段是可访问的,并且保证段偏移在段的大小范围内。
  3. 段描述符中的段的基址段偏移量相加,形成一个线性地址

​ 如果不使用分页机制,处理器将会将线性地址直接映射为物理地址(也就是说,也就是说,线性地址可以直接送到处理器的地址总线,也就是说,线性地址等同于物理地址)。 如果线性地址空间被分页,启用分页机制,则会使用第二级地址转换来将线性地址转换为物理地址。

2.3.1 段选择子(Segment Selectors)

​ 一个段选择子是一个段的16位标识符(如图6所示)。段选择子不直接指向一个段,反而指向定义了一个段的段描述符。一个段选择子包含了如下几项信息:

  • Index:索引(第3到15位),共13位,可从GDT或LDT中8192($2^{13}$)个段描述符中选择一个。处理器将索引值乘以8(一个段描述符的大小为8字节),并将其与GDT或LDT的基址(分别来自GDTR或LDTR寄存器)相加可定位一个段描述符。
  • TI (table indicator) flag :表指示标志位(第2位),指示所使用段描述符表,当复位时,表示选择GDT;设置该表示位时,表示选择LDT。
  • Requested Privilege Level (RPL) :请求特权级别(第0和第1位),共两位,指示该段选择子的权限级别。权限级别的范围为0到3,权限级别0为最高权限级别

图6 段选择子

​ 处理器不使用GDT的第一个表项。 指向这个GDT表项的段选择子(即索引值为0且TI标志设置为01的段选择子)称为“空段选择器”。 当空选择器加载入段寄存器(除CS或SS寄存器(代码段寄存器、堆栈段寄存器)外)时,处理器不会产生异常。 然而,当使用存储空选择器的段寄存器访问内存时,它会产生一个异常。 空选择器可以用于初始化未使用的段寄存器。 用空段选择器加载CS或SS寄存器会导致生成一个通用保护异常(#GP)。
​ 段选择子作为指针变量的一部分对应用程序可见,段选择子的值通常由链接编辑器或链接加载器分配或修改,而不是应用程序。

2.3.2 段寄存器(Segment Registers)

​ 为了减少地址转换时间和降低编程复杂度,处理器提拱了至多可存储6个段选择子的寄存器(如图7所示)。每个段寄存器支持一种特定类型的内存引用(代码、堆栈或数据)。对于执行任何类型的程序,至少要将有效的段选择符加载到代码段(CS)、数据段(DS)和堆栈段(SS)。处理器还提供另外的数据段寄存器(ES、FS、GS),可贝用于让当前正在执行的程序(或任务)能够访问其他几个数据段。
​ 对于访问一个段的一个程序,段选择器必须已经加载到一个段寄存器中。 因此,尽管一个系统可以定义上千个段,但只有6个段可立即访问使用。 在程序执行期间,通过将其他段的段选择子加载到这些寄存器中,就可以访问其他段。

图7 段寄存器

​ 每个段寄存器都有一个“可见”部分和一个“隐藏”部分 (隐藏部分有时被称为“描述符缓存”“影子寄存器”)。当一个段选择符被加载到段寄存器的可见部分时,处理器会同时将段选择符所指向的段描述符的段基址、段限长、 以及访问控制信息加载到段寄存器的隐藏部分。 段选择符缓存在段寄存器中的信息(可见和隐藏部分)使得处理器可以直接进行地址转换,而不需要再花费额外的总线周期从段描述符读取段的基址和段限长信息。 在一个多处理器系统中,处理器都访问同一个描述符表,如果描述表被修改,则软件应负责重新加载段寄存器。 如果不重新加载,则可能出现段描述符已经被修改,却仍在使用缓存在段寄存器中的旧段描述符的清空。
​ 提供了以下两种方法来加载段寄存器:

  • 使用MOV,POP,LDS,LES,LSS,LGS和LFS 等指令加载。这些指令显式地引用段寄存器。
  • 隐式加载指令,例如使用长指针方法的CALL,JMP和RET指令,还有SYSENTER和SYSEXIT指令,IRET,INT n,INTO和INT3指令。这些指令执行时会附带修改CS寄存器的内容(有时也会修改其它寄存器的内容)。
  • MOV指令也可被用于把段寄存器的可见部分的内容加载到一个通用目的寄存器。

2.3.3 段描述符(Segment Descriptors)

​ 一个段描述符是GDT或LDT中的一个数据结构,其提供处理器中一个段的大小、位置、访问控制和状态信息。段描述符通常由编译器、链接器、加载器或操作系统或执行程序创建,而不是应用程序创建。图8展示了所有类型的段描述符的通用描述符格式。

图8 段描述符

​ 一个段描述符的标志位和字段信息如下:

  • Segment limit field :段限长字段,指明一个段的大小。处理器会把两个段限长字段拼接形成一个20位长的值。并根据颗粒度标志位G的两种不同的值来解释段限长字段的含义。
    若G=0,段大小的范围为1字节到1MB字节,单位为1字节。若G=1,段大小的范围为4KB到4GB字节,单位为4KB字节。
    处理器通过两种不同方式使用段限长,其依据是段是向上扩展段还是向下扩展段。 对于向上拓展的段,逻辑地址的段偏移量的范围为0字节到段限长值。大于段限长的偏移量将会产生一个一般保护异常(#GP,所有段都会产生除了堆栈段外)或一个堆栈错误异常(#SS表示SS段) 。对于一个向下拓展段,则相反,段偏移量的范围为段限长1字节增长到0xFFFFFFFFH或0xFFFFH(这个取决于B标志位的设置)。小于或等于段限制的偏移量会产生一般保护异常或堆栈故障异常。对于向下拓展段,减少段限长的值允许在段地址空间底部分配新的内存,而不是再顶部。因为IA-32架构所使用的栈总是向下增长的,所以这种机制便于栈的扩展。
  • Base address fields :基址字段,该字段定义了段的0字节在4GB大小的线性地址空间中的位置。 处理器将三个基址字段拼接在一起形成一个32位长的值。 段基址应按16字节边界进行对齐。 虽然16字节对齐不是必需的,但是通过把程序的代码和数据按16字节边界上对齐,可使得程序性能最佳。(即,边界对齐便于数据的访问)
  • Type field :类型字段,指示该段或门的类型,并指明段的访问类型和段的扩展方向。根据描述符类型标志位,这个字段会有两种含义:应用程序(数据、代码)段描述符系统段描述符。对于代码段、数据段、系统段,这个字段的编码并不相同。
  • S (descriptor type) flag:描述符类型标志位,其指明了一个段描述符是一个系统段描述符(该标志位复位)还是一个代码或数据段描述符(该标志位被设置)。
  • DPL(descriptor privilege level )field:指明该段的特权级别。特权级别的取值范围为0 -30是权限级别最高。DPL用于控制对段的访问。
  • P(segment-present) flag:段存在标志位,指明段是否于内存中存在(P=1,在内存中;P=0,不存在内存中)。若P=0,当一个段描述符的段选择符加载进段寄存器时处理器会产生一个段不存在异常。内存管理软件可以使用此标志位来控制在某一给定时间将指定的段加载进物理内存中,这为管理虚拟内存提供了除分页以外的控制。图9展示了P=0时一个段描述符的格式。当复位该标志位时,操作系统或执行程序可以自由使用格式中标为“可用”(Available)的位置来存储自己的数据,例如有关不存在段实际在什么地方的信息。
  • D/B(default operation size/default stack pointer size and/or upper bound) flag :D/B(默认操作大小/默认栈指针大小和/或上界限)标志,其根据段描述符描述的是一个可执行代码段、还是下扩数据段、或堆栈段来执行不同的功能。(对于32位代码和数据段,该标志位应该总被设置为1;对于16位数据和代码段,该标志段被设置为0)。
    • 可执行代码段:此时这个标志被称为D标志位,其指明段中的指令引用有效地址和操作数的默认长度。当D=1,则默认值为32位地址和32位或8位操作数;当D=0,则默认值为16位地址和16位或8 位操作数。指令前缀0x66H可用来选择非默认值的操作数大小;指令0x67H可用来选择非默认值的操作地址大小。
    • 堆栈段(由SS寄存器指向的数据段):此时这个标志被称为B标志位,它用于指明隐含栈操作(如pushes、pops、calls)时的栈指针的大小。当B=1,则使用一个32位的栈指针,它存储在32位的ESP寄存器中;假如B=0,则使用一个16位的栈指针,它存储在16位的SP寄存器中。假如堆栈段被设置成一个下扩数据段,那么B标志也同时指定了堆栈段的上界限。
    • 下扩段(Expand-downdata segment)此时标志叫作B标志,它指明堆栈段的上界。当B=1,上界为0xFFFFFFFFH(4Gbytes);当B=0,上界为0xFFFFH(64KBytes)。
  • G (granularity) flag :颗粒度标志位G,其决定了段限长域字段的单位。当G=0时,段限长的单位为字节;当G=1时,段限长的单位为4KB(这个标志位不会影响基地址的颗粒度,基地址的颗粒度总是字节单位。)。若设置了该标志位,则在使用段限长检查偏移量时,不会检查偏移量的12位最低有效位。 例如,当G=1时,段限长为0表明有效偏移量为0到4095。
  • L(64-bit code segment) flag :L标志位,在IA-32e模式下,段描述符第二个双字的第21位表示该代码段是否存储了本地64位代码。 当L=1时,表示在64位模式下执行该代码段中存储的指令。 当L=0时。表示在兼容模式下执行此代码段中存储的指令。 如果设置了L位,则必须复位D标志位。 当在非IA-32e模式或该段非代码段时,第21位是保留的,并且应该总是设置为0。
  • Available and reserved bits :可用和保留位,段描述符的第二个双字的第20位是保留给操作系统软件使用的;

图9 P=0时的段描述符

2.4描述符的分类

数据段和代码段描述符(Data and Code segment Descriptor)

​ 当段描述符中的S标志位被设置时,该描述符描述的是一个代码或数据段。类型字段(type)的最高有效位(第二个双字的第11位)用于决定是一个数据段(复位)还是一个代码段描(置位)。
​ 即:

  • 当S=1,type的最高有效位为0时,该段描述符指向一个数据段;
  • 当S=1,type的最高有效位为1时,该段描述符指向一个代码段。

​ 对于数据段描述符,类型字段的低3位(第8、9、10位)的含义分别为被访问accessed (A)、可写write-enable (W)和拓展方向expand -direction (E)。表1展示了代码段和数据段的类型字段比特位编码的说明。根据可写比特位W的设置, 数据段可以是只读的,也可以是可读/写的。

表1 代码段和数据段描述符类型

堆栈段必须是一个可读/写的数据段。若将不可写数据段的段选择符加载到 SS 寄存器中,则会导致产生一个一般保护异常。如果堆栈段的长度会动态变化,那么堆栈段可以是一个向下扩展的数据段(扩展方向标志被设置)。因而,动态改变段限长的值将导致栈空间被添加到栈底部。 如果希望堆栈段的大小保持不变,则堆栈段可以是向上拓展或向下拓展类型。
​ 被访问位A指明该段自上次操作系统或执行程序复位该位后是否被访问过。 如果包含段描述符的内存类型支持处理器写操作,那么处理器每次将段选择符加载到段寄存器时都会设置这个位。 该位会一直保持被设置状态,直到其显式复位。 该标志位既可以用于虚拟内存管理,也可以用于调试。
​ 对于代码段,类型字段的低三位的含义(第8、9、10位)分别为被访问accessed (A)、可读read enable (R)、 一致conforming (C)。代码段可以是只能执行或可执行/可读,这取决于可读位的设置。 当常数或其他静态数据以及指令码存储在了一个ROM 中时,就可以使用一个可执行/可读代码段。 通过使用带CS前缀的指令或者把代码段选择符加载进一个数据段寄存器(DS、ES、FS或GS寄存器),就可读取代码段中的数据。 在保护模式下,代码段是不可写的。
​ 代码段可以是一致性和非一致性的。操作系统允许执行在当前特权级的程序向一个更高权级的一致性代码段进行转移。当向一个不同特权级别非一致性代码段进行执行转移时,会产生一个通用保护异常(#GP),除非使用调用门(call gate)或任务门(task gate)(这个我们已经在读书笔记1中,有所了解,通过门,可以进行跨权限级别的访问)。一些系统工具(不访问保护设施)和一些处理某些异常类型(如除出错,溢出)的处理程序可以放在一致性代码段内。需要保护其不能被更低特权级程序访问的程序应该放在非一致性代码段内。
​ 所有的数据段都是非一致性的,这意味着它们不能被更低特权级(权限数字值较大)的程序或过程访问。与代码段不同的是,数据段总是能被更高特权级(权限数字值较小,数字越小,权限越高)的程序或过程访问,而不需要使用特别的访问门。
​ 当GDT或LDT中的段描述符存储在在ROM中时,若软件或处理器试图更新(写入)位于ROM的段描述符,则处理器就会进入一个无限循环。 为了避免该问题发生,将ROM中所有段描述符的访问位都置位。同时,会将那些试图修改ROM中段描述符的操作系统代码或执行代码删除掉。
注意:不能通过call或jump转入更低权限级别(权限数字值较大)的代码段执行,不管目标段是一个一致性还是非一致代码段。这种转移执行的尝试操作,将会引起一个一般保护异常。

系统描述符类型(SYSTEM DESCRIPTOR TYPES )

​ 当段描述符中的S标志位(描述符类型)被复位时,表明该段描述符为一个系统描述符。处理器能识别以下一些类型的系统段描述符:

  • 本地描述符表(LDT)的段描述符

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

  • 调用门描述符

  • 中断门描述符

  • 陷阱门描述符

  • 任务门描述符

    这些描述符可分为两大类:系统段描述符和门描述符。系统段描述符指向一个系统段(LDT或TSS段)。门描述符就是他们自己——“门”,这些门描述符存储了代码段中指向程序入口点的指针(调用、中断和陷阱门),或者存储了TSS的段选择符(任务门)。 图2占了系统段描述符和门描述符类型字段的编码及说明。

表2 系统段描述符类型
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2019-2022 1nvisble
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信