保护模式初级

前言

本章为之前学习《操作系统真象还原》一书所做的笔记

内容为第四章、第五章的保护模式

全局描述符表

全局描述符表( Global Descriptor Table, GDT )是保护模式下内存段的登记表,这是不同于实模式的显著特征之一

段描述符

首先,对于 IA32 架构的处理器(就是我们大多数人现在所用的处理器),访问内存采用“段基址:段内偏移地址”形式,即使到了保护模式,也是绕不开这个限制的

同时,为了保证内存访问的安全问题,保护模式应运而生

在之前的16位模式下,内存访问的上限收到了寄存器的限制,为此人们甚至需要将16位寄存器左移4位来扩大寻址空间,由此,通过内存寻址的想法就有了,因为其不受寄存器大小限制,而且还可以为段添加上更多的描述信息

段描述符如下:

image.png

段描述符的大小为8字节64位

  • 段界限
    • 段界限表示段边界的扩展最值,以限制内存访问的范围
    • 在代码段和数据段,段的扩展方向是向上,在栈段,扩展方向为向下
    • 可以看到段界限由20个比特位共同描述,而最终段的边界为段界限值 * 单位,单位要么为1字节,要么为4kb(2^12次方字节),所以段大小要么为2^20=1MB大小,要么为2^20 * 2^ 32=4GB大小
    • 单位大小由G位决定,G为0则大小为1byte,G位为0则大小为4kb
    • 访问若超出段的边界,CPU就会抛出异常
  • 段基址
    • 16-31位存储段基址的0-15位,高32位中,0-7位存储段基址的16-23,24-31存储段基址的24-31
  • S位
    • S位用于描述是系统段还是数据段,在 CPU 眼里,凡是硬件运行需要用到的东西都可称之为系统,凡是软件(操作系统也属于软件, CPU 眼中,它与用户程序无区别)需要的东西都称为数据,无论是代码,还是数据,它们都作为硬件的输入,都是给硬件的数据而己,所以代码段在段描述符中也属于数据段(非系统段)
    • S位为0,系统段,S位为1,数据段(非系统段)
    • S位需要和type字段配合使用
  • type字段
    • type字段共4位,用于表示内存段或门的子类型
    • type字段说明如下图所示:
      image.png
    • 主要关注非系统段
    • A位表示Accessed位,这是由CPU来设置的,每当该段被CPU访问过后,CPU就将此位置1,所以,创建个新段描述符时,应该将此位置0
    • C表示一致性代码段,也称为依从代码段, Conforming。一致性代码段是指如果自己是转移的目标段,并且自己是一致性代码段,自己的特权级一定要高于当前特权级,转移后的特权级不与自己的 DPL 为主,而是与转移前的低特权级一致,也就是昕从、依从转移前的低特权级。 C为1时则表示该段是一致性代码段, C为0时则表示该段为非一致性代码段
    • R表示可读,R为1表示可读,R为0表示不可读。这个属性一般用来限制代码段的访问
    • X表示该段是否可执行,EXecutable。X为1表示代码段可执行,X为0表示代码段不可执行
    • E是用来标识段的扩展方向,Extend。E为0表示向上扩展,通常用于代码段和数据段。E为1表示向下扩展,地址越来越低,通常用于栈段
    • W是指段是否可写, Writable。W 为1表示可写,通常用于数据段。W为0表示不可写入,通常用于代码段
  • DPL
    • 段描述符的第 13-14 位是 DPL 字段, Descriptor Privilege Level ,即描述符特权级,指描述符所代表的内存段的特权级
    • 2个比特能表示四个特权级,即0,1,2,3,数字越小特权级越大。操作系统处于最高的0,用户程序处于3,某些指令只能在0特权级下执行,从而保证了安全
  • P字段
    • 段描述符的第 15 位是P字段,Present,即段是否存在,1为存在,0为不存在
  • AVL字段
    • 占一位,表示Available,可用的
  • D/B字段
    • 段描述符的第 22 位是 D/B 字段,用来指示有效地址(段内偏移地址)及操作数的大小
    • 对于代码段来说,此位是D位,若D为0,表示指令中的有效地址和操作数是 16 位,指令有效地址用IP寄存器。若D为1,表示指令中的有效地址及操作数是 32 位,指令有效地址用 EIP 寄存器
    • 对于栈段来说,此位是B位,用来指定操作数大小,此操作数涉及到栈指针寄存器的选择及栈的地址上限。若B为0,使用的是 sp 寄存器,也就是栈的起始地址是 16 位寄存器的最大寻址范围, 0xFFFF。若B为1,使用的是 esp 寄存器,也就是栈的起始地址是 32 位寄存器的最大寻址范围, 0xFFFFFFFF
  • G字段
    • 段描述符的第 23 位是G字段, Granularity ,粒度,用来指定段界限的单位大小

全局描述符表GDT、局部描述符表LDT及选择子

全局描述符表 GDT 相当于是描述符的数组,数组中的每个元素都是8宇节的描述符。可以用选择子中提供的下标在 GDT 中索引描述符

全局描述符表位于内存中,有专门的GDTR寄存器指向其地址,GDTR是个48位的寄存器,如图所示

image.png

由专门的指令lgdt来修改GDTR寄存器

lgdt指令在实模式和保护模式下都可执行,在保护模式下重新换个 GDT 的原因是实模式下只能访问低端 1MB 空间,所以 GDT 只能位于 1MB 之内

lgdt 的指令格式是: lgdt48 位内存数据

48 位内存数据划分为两部分,其中前 16 位是 GDT 以字节为单位的界限值,所以这 16 位相当于GDT 的字节大小减1。后 32 位是 GDT 的起始地址。由于 GDT 的大小是 16 位二进制,其表示的范围是16 次方等于 65536 字节。每个描述符大小是8字节,故GDT 中最多可容纳的描述符数量是 65536/8=8192个,即 GDT 中可容纳 8192 个段或门

由于段基址存入了段描述符中,此时段寄存器中存入的是一个叫做选择子的东西——selector,用于在GDT中索引相应的段描述符

段选择子结构如下:

image.png

  • RPL
    • 0-1位用于存储RPL,由于是2位,因此可以表示0,1,2,3四个特权级
    • RPL即请求特权级,可以理解为请求者的当前特权级
  • TI位
    • 第2位是TI位,即 Table Indicator ,用来指示选择子是在 GDT 中,还是 LDT 中索引描述符
    • 0表示在GDT中索引描述符,1表示在LDT中索引描述符
  • 索引值
    • 选择子的3-15位是描述符的索引值,用此值来索引描述符
    • 2^13=8192,故最多可以索引8192个描述符

本质上还是通过段基址+偏移的方式来完成内存访问的,段描述符与内存段的关系:

image.png

值得注意的是,GDT 中的第0个段描述符是不可用的,原因是定义在 GDT 中的段描述符是要用选择子来访问的,如果使用的选择子忘记初始化,选择子的值便会是0,这便会访问到第0段描述符。为了避免出现这种因忘记初始化选择子而选择到第0个段描述符的情况,GDT 中的第0个段描述符不可用。也就是说,若选择到了 GOT 中的第0个描述符,处理器将发出异常

局部描述符表,叫 LDT, Local Descriptor Table,其为支持多任务而创建,按照CPU的设想为一个任务对应一个LDT,但其实现代操作系统LDT并不多用

LDT表也需要加载到LDTR寄存器中,加载指令为lldt

lldt指令的格式为:lldt 16 位寄存器/16 位内存

LDT 为系统段,其也是一片内存区域,所以也需要用个描述符在 GDT 中先注册

同时LDT表的第0项是可用的,因为TI位为显式初始化的必然结果

打开A20地址线

在实模式下,存在wrap-around,即地址回绕的问题,也就是最大寻址为0xffff0+0xffff=0x10ffef,显然这已经超过了1MB的内存

所以CPU的做法是会从头开始,产生回绕,相当于把地址对1MB求模 超过1MB但多余出来的内存被称为高端内存区 HMA

当CPU到了80286时候,地址总线由20位变成了24位,出于兼容性的考虑,便有了A20Gate,即对第21根地址线进行控制

  • 如果 A20Gate 被打开,当访问到 0x100000-0x10FFEF 之间的地址时, CPU 将真正访问这块物理内存
  • 如果 A20Gate被禁止,当访问到 0x100000-0x10FFEF 之间的地址时, CPU 将采用 8086/8088 的地址回绕

开启A20Gate的方式也极其简单,将端口0x92的第1位置1:

1
2
3
in al, 0x92
or al, 00000010B
out 0x92, al

保护模式开关

CRx系列控制寄存器是CPU的窗口,用来展示CPU内部状态及控制CPU运行机制

保护模式的开关为CR0寄存器:

image.png

CR0寄存器的第0位为PE位,即Pro tionEnable ,此位用于启用保护模式

开启保护模式的最后一步:

1
2
3
mov eax, cr0
or eax, 0x00000001
mov cr0, eax

保护模式之内存段的保护

向段寄存器加载选择子时的保护

当引用一个内存段时,实际上就是往段寄存器中加载个选择子,处理器会做出一些检查

  • 选择子检查
    • 需要判断高13位的索引值是否正确,一定要小于等于描述符表中的描述符个数,了一通过段描述符表的界限值来判断
    • 先检查TI的值,选择是从GDT还是LDT中取描述符
  • 段描述符检查
    • 检查type字段,检测段寄存器的用途和段类型是否匹配,原则如下:
      • 具有可执行属性的段才能加载到CS
      • 只具备执行属性的段不允许加载到除CS外的段寄存器
      • 具备可写属性的段才能加载到SS栈段
      • 至少具备可读才能加载到DS、ES、FS、GS段寄存器中
      • 总结,如下表所示:
        image.png
    • 检查完type后检查p位,验证该段是否存在
    • 设置A位为1,表示CPU已访问过
    • A位由CPU设置,P位通常由操作系统设置