Post

80X86保护模式及其编程

80X86保护模式及其编程

1. 80X86系统寄存器和系统指令

标志寄存器EFLAGS

  • 系统标志IOPL字段用于控制I/O访问、可屏蔽硬件中断、调试、任务切换以及虚拟-8086模式
    • 一般只允许操作系统代码有权修改这些标志
  • 其他标志位为一些通用标志 标志寄存器EFLAGS
  • TF:跟踪标志
    • 当设置该位时可位调试操作启动单步执行方式;复位时则禁止单步执行
    • 处理器会在每个指令执行之后产生一个调试异常,方便调试观察处理器的状态
  • IOPL:指明当前运行程序或任务的I/O特权级
    • 当前运行程序或任务的CPL必须<=这个IOPL才能访问I/O地址空间

内存管理寄存器

  • GDTR、LDTR、IDTR、TR
    • 用于指定分段内存管理所时使用的系统表的基地址
    • 处理器为这些寄存器的加载和保存提供了特定的指令

内存管理寄存器

  • 全局描述符表寄存器GDTR
    • 存放全局描述符表GDT的32为线性基地址和16位表长度值
    • 指令LGDT:加载GDTR的内容
    • 指令SGDT:保护GDTR的内容
  • 中断描述符表寄存器IDTR
    • 存放中断描述符表IDT的32为线性基地址和16位表长度值
    • 指令LIDT:加载GDTR的内容
    • 指令SIDT:保护GDTR的内容
  • 局部描述符表寄存器LDTR
    • 存放局部描述符表LDT的32为线性基地址和16位表长度值和描述符属性值
    • 指令LLDT、SLDT加载和保护LDTR的段描述符部分
  • 任务寄存器TR
    • 存放当前任务TSS段的16位段选择符、32位基地址、16位段长度和描述符属性值
    • 指令LTR、STR加载和保存TR的段选择符部分
    • 执行任务切换时,处理器会把新任务TSS的段选择符和段描述符自动加载到任务寄存器TR中

控制寄存器

  • 控制寄存器用于控制和确定处理器的操作模式以及当时执行任务的特性
    • CR0中含有控制处理器操作模式和状态的系统控制标志
      • 设置CR0的PE标志:处理器工作在保护模式下
      • 设置CR0的PG标志:开启分页机制,同时也开启了分页保护机制
        • 可以通过设置每个页目录项和页表项的R/W标志和U/S标志来禁止页级保护机制
    • CR1保留不用
    • CR2含有导致页错误的线性地址
    • CR3含有页目录表物理内存基地址(也称页目录基地址寄存器PDBR)

控制寄存器

系统指令

  • 用于处理系统级功能,例如加载寄存器、管理中断等
  • 大多数系统指令只能由处于特权级0的操作系统软件执行
  • 其余一些可以在任何特权级上执行

2. 保护模式内存管理

内存寻址

  • 内存是指一组有序字节组成的数组,每个字节有唯一的内存地址
  • 对于80X86 CPU而言,地址总线宽度为32位,内存物理地址空间有4G
  • 存放方式:首先存放低值字节,随后存放高值字节(小端法
  • 寻址方式:
    • 16位的段地址+32位的段内偏移地址 = 48位逻辑地址(虚拟地址)
    • 一个段的最大长度可达4G
  • 段寄存器:
    • CS:CS寻址的段为当前代码段
      • 此时EIP寄存器中包含了当前代码段内下一条要执行指令的段内偏移地址
      • CS:[EIP]:需要执行指令的地址
    • SS:SS寻址的段为当前堆栈段
      • ESP寄存器指定栈顶
      • SS:[ESP]:堆栈顶处地址
    • DS:默认的数据段寄存器
    • ES:扩展寄存器
    • 其他:ES、FS、GS
  • 计算偏移量
    • 偏移地址 = 基地址 + (变址x比例因子) + 偏移量

地址变换

内存管理机制将逻辑地址转换成物理内存地址

  • 通常以内存块作为操作单位
  • 两种地址变换技术
    • 分段机制分页机制
    • 都使用驻留在内存中的表来指定它们各自的变换信息
  • 80x86同时使用这两种机制 地址变换过程
分段机制
  • 一个系统中所有使用的段都包含在处理器线性地址空间中 逻辑地址、线性地址和物理地址之间的变换
  • 如果禁用分页机制,则线性地址空间就是物理地址空间
分页机制
  • 分页机制支持虚拟存储技术
  • 大容量的线性地址空间需要使用小块的物理内存以及某些外部存储空间来模拟
  • 每个段被划分成页面(通常每页为4KB),页面会被存储于物理内存或硬盘上

保护

  • 任务之间的保护
    • 把每个任务放置在不同的虚拟地址空间,不同任务的逻辑地址映射到不同的物理地址,每个任务有各自独立的映射表,每个任务有不同的地址变换函数
    • 任务切换的关键部分就是切换到新任务的变换表
    • 全局地址空间:相同的虚拟到物理地址映射的空间部分,被所有任务共享
    • 局部地址空间:每个任务唯一的虚拟地址空间,存放私有的代码和数据。相同虚拟地址转换到不同的物理地址
  • 特权级保护
    • 4个执行特权级:0具有最高特权级,3则是最低特权级
    • 每个内存段都与一个特权级相关联
    • CPL:当前活动代码段的特权级。允许访问同级别或低级别的数据段

3. 分段机制

把虚拟地址空间中的虚拟内存组织成一些长度可变的内存单元,称为段

参数说明
段基地址段在线性地址空间中的开始地址
段限长虚拟地址空间中段内最大可用的偏移位置,段的长度
段属性段的特性,例该段是否可读、可写、可执行;段的特权级等

虚拟地址空间中的段映射到线性地址空间

  • 可映射到重叠区域

段描述符

  • 段的基地址、段限长及段的保护属性存储在段描述符的结构项
  • 格式(8字节) 段描述符通用格式
  • G字段:用于确定段限长字段Limit值的单位
    • G=0:单位为字节
    • G=1:单位4KB
  • 段描述符通常由编译器、链接器、加载器或者操作系统来创建,但绝不是应用程序
  • 逻辑地址到线性地址的转换
    1. 使用段选择符中的偏移值在GDT或LDT中定位相应的段描述符
    2. 利用段描述符检验段的访问权限和范围,以确保该段是可访问的并且偏移量位于段界限内
    3. 段描述符中取得的段基地址加到偏移量上,最后形成一个线性地址 逻辑地址到线性地址
代码和数据段描述符

S标志被置位

  • 对于数据段
    • 类型字段的低3位(位8、9、10)被分别用于表示已访问A、可写W、和扩展方向E
    • 堆栈段必须是可读/写的数据段
  • 对于代码段
    • 低3位被解释为已访问A、可读R和一致的C
系统描述符

S标志位是复位状态(0)

  • 系统段描述符
    • 指向系统段(LDT、TSS段等)
  • 门描述符
    • 对于调用、中断或陷阱门,包含代码段的选择符和段中程序入口点的指针
    • 对于任务门,其中含有TSS的段选择符

段描述符表

  • 段描述符的数组,且长度可变
  • 全局描述符表GDT局部描述符表LDT
  • 整个虚拟地址空间共含有$2^{14}$个段
    • 一半$2^{13}$由GDT映射的全局虚拟地址空间,另一半由LDT映射映射的局部虚拟地址空间

段描述符表结构

  • 发生任务切换时,LDT会更新成新任务的LDT,但是GDT不会改变。所以说,GDT所映射的一半虚拟地址空间是系统中所有任务共有的 任务所用的段类型
  • 每个系统必须定义一个GDT,并可用于系统中所有程序或任务
    • GDT本身并不是一个段,而是线性地址空间中的一个数据结构
    • 处理器并不使用GDT中的第1个描述符
  • LDT表存放在LDT类型的系统段中
    • GDT必须含有LDT的段描述符

段选择符

段选择符结构

  • 组成
    • 请求特权级RPL
      • 提供段保护信息
    • 表指示标志TI
      • TI=0:描述符在GDT中
      • TI=1:描述符在LDT中
    • 索引值
  • 选择符的值通常是由链接器进行设置或修改的,而非应用程序

段寄存器

段寄存器结构

  • 对于访问某个段的程序,必须已经把段选择符加载到一个段寄存器中
  • 隐藏部分可缓冲段的信息,避免每次访问内存都去引用描述符表

4. 分页机制

将线性地址转换为物理地址

  • 设置寄存器CR0的PG位可以启用分页机制
    • PG=1:启用分页机制
    • PG=0:禁用分页机制,线性地址直接用作物理地址
  • 分页机制对固定大小的内存块进行操作
    • 80x86的页面大小固定为4KB,分成$2^{20}$个页面
    • 线性地址的低12位作为页内偏移量直接作为物理地址的低12位
  • 其将线性和物理地址空间都划分成页面,线性地址空间中的任何页面可以被映射到物理地址空间的任何页面上

页表结构

  • 页表中的每个页表项大小为32位
    • 其中只需要20位来存放页面的物理地址,剩余的12位用于存放属性信息
  • 两级页表结构
    • 第一级:页目录
      • 存放在1页4KB页面中,具有$2^{10}$个4字节长度的表项
      • 这些表项指向对应的二级表
    • 第二级:页表
      • 长度也是1个页面,每个4字节表象含有相关页面的20位物理基地址

线性地址和物理地址的转换

  • 页目录和页表的表象中都有一个存在属性。
    • 若该属性标注为不存在,则处理器产生一个异常来通知操作系统
  • 页表项格式 页目录和页表的表项格式
  • P:存在标志位,用于指明表项对地址转换是否有效
  • R/W:读写标志
    • 若为1:表示页面可以被读、写或执行
    • 若为0:表示页面只读或可执行
  • U/S:用户/超级用户标志
    • 若为1:运行在任何特权级上的程序都可以访问该页面
    • 若为0:只能被运行在超级用户特权级(0、1或2)上的程序访问
  • A:已访问标志
  • D:已修改标志:执行写操作时设置
  • AVL:保留专供程序使用

虚拟存储

  • 若表项中的标志P=0,则处理器产生一个缺页异常。操作系统把缺少的页面从磁盘上调入物理内存中,并将相应物理地址存放在表项中,然后设置P=1
  • 周期性检查和复位所有A标志
    • 操作系统能够确定哪些页面最近没有访问,然后这些页面便可能被移出到磁盘上(必要的话)
  • 检查D标志
    • 当页面再次被移出到磁盘上,若D=0,则该页面就无需被写入磁盘;若D=1,说明内容已经被修改过,于是必须将该页面写到磁盘上

5. 保护

段级保护

  • 每个内存引用都将受到检查以验证内存引用符合各种保护要求
    • 检查操作与地址变换同时并行操作
  • 保护检查分类
    • 段界限检查、段类型检查、特权级检查、可寻址范围限制、过程入口点限制、指令集限制
  • 所有违反保护的操作都将导致产生一个异常
段限长Limit检查
  • 防止程序寻址到段外内存位置
  • 段限长的有效值依赖于颗粒度G标志的设置状态
段类型TYPE检查
  • 当操作段选择符和段描述符时,处理器会随时检查类型信息
    1. 当一个描述符的选择符加载进一个段寄存器中。某些段寄存器只能存放特定类型的描述符:
    1. CS寄存器只能被加载进一个可执行段的选择符
    2. 不可读可执行段的选择符不能被加载进数据段寄存器中
    3. 只有可写数据段的选择符才能被加载进SS寄存器中
      1. 当指令访问一个段,而该段的描述符已经加载进段寄存器中。指令只能使用某些预定义的方法来访问某些段
    4. 任何指令不能写一个可执行段
    5. 任何指令不能写一个可写位没有置位的数据段
    6. 任何指令不能读一个可执行段,除非可执行段设置了可读标志
特权级

处理器特权级示意图

  • 防止运行在较低特权级的程序或任务访问具有较高特权级的一个段
  • 分类
    • 当前特权级CPL
      • 存放在CS和SS段寄存器的位0和位1中
      • 当程序把控制转移到另一个具有不同特权级的代码段中,处理器就会改变CPL
    • 描述符特权级DPL
      • DPL是一个段或门的特权级,存放在段或门描述符的DPL字段中
    • 请求特权级RPL
      • 存放在选择符的位0和位1
      • 始终取RPL和CPL中数值最大的特权级作为访问段时的比较对象
  • 访问数据段时的特权级检查
    • 只有当段的DPL数值>=CPL和RPL两者时,处理器才会把选择符加载进段寄存器中,否则会产生保护异常,并且不加载选择符

访问数据段的特权级检查

页级保护

  • 普通用户级(特权级3)
    • 页面被标志成只读/可执行、或者可读/可写/可执行
  • 超级用户级(特权级0、1和2)
    • 页面总是可读/可写/可执行
  • 映射问题
    • 页表表项中的U/S标志和R/W标志用于该表项映射的单个页面
    • 页目录项中的U/S标志和R/W标志对目录项所映射的所有页面起作用
  • 页级保护不能代替或忽略段级保护

6. 中断和异常处理

异常和中断向量

  • 向量:中断描述符表IDT中的一个索引号,来定位一个异常或中断的处理程序入口点位置
  • 允许的向量号范围0到255
    • 0到31保留用作80X86处理器定义的异常和中断
    • 32到255的向量号用于用户定义的中断
  • 中断指令
    • 开中断:sti
    • 关中断:cli

中断源

  • 外部中断(硬件产生的)
    • 通过处理器芯片上的两个引脚(INTR和NMI接收)
    • 非屏蔽中断:NMI接收的外部中断,固定中断向量号2
    • 可屏蔽中断:INTR接收的外部中断
    • EFLAGS的IF标志可用来屏蔽所有的这些硬件中断
  • 软件产生的中断
    • INT n指令可调用中断号n

异常源

  • 处理器检测到的程序错误异常
  • 软件产生的异常
    • 指令INTOINT3BOUND可用来从软件中产生异常

异常分类

  • 故障(Fault)
    • 通常可以被纠正的异常
    • 并且一旦纠正程序就可以继续运行
      • 原出错指令会被重新执行,例如缺页故障
  • 陷阱(Trap)
    • 引起陷阱的指令被执行后立刻会报告的异常
    • 也能让程序或任务连贯地执行
      • 处理器产生异常时保存的返回指针指向引起陷阱操作的后一条指令
      • 控制转移指令则指向跳转的目标位置
  • 中止(Abort)
    • 用于报告严重错误
    • 不允许导致异常的程序重新继续执行

中断描述符表IDT

  • IDT将每个异常或中断向量分别与它们处理过程联系起来
  • IDT也是一个由8字节长描述符组成的数组
  • 使用IDTR寄存器来定位IDT表的位置 IDT和IDTR
IDT描述符
  • 中断门描述符
    • 含有长指针,用来转移执行权给中断处理程序
  • 陷阱门描述符
    • 同上
  • 任务门描述符 IDT描述符格式

异常与中断处理

  1. 处理器使用异常或中断的向量作为IDT表中的索引
  2. 若索引值指向中断门或陷阱门,则处理器使用与CALL指令操作调用门类似的方法调用异常或中断处理过程
  3. 若索引值指向任务门,则处理器使用与CALL指令操作调用任务门类似的方法方法进行任务切换 中断过程调用 转移到中断处理过程时堆栈使用方法

中断处理任务

  • 使用任务门来访问异常或中断处理过程 中断处理任务切换
  • 被中断程序或任务的上下文会自动保护
  • 响应速度慢

错误码

  • 当异常条件与一个特定的段相关时,处理器会把一个错误码压入异常处理过程的堆栈上

7. 任务管理

  • 任务是处理器可以分配调度、执行和挂起的一个工作单元

任务的结构和状态

  • 组成
    • 任务执行空间
      • 代码段、堆栈段和数据段
    • 任务状态段TSS
      • 指定了构成任务执行空间的各个段,并且为任务状态信息提供存储空间

任务的结构和状态

  • 当一个任务被加载到处理器中执行,那么该任务的段选择符、基地址、段限长及TSS段描述符属性就被加载到任务寄存器TR
  • 若使用分页机制,则任务使用的页目录表基地址就会被加载到CR3

任务的执行

  • 使用CALL指令明确地调用一个任务
  • 使用JMP指令明确地跳转到一个任务
  • (由处理器)隐含地调用一个中断句柄处理任务
  • 隐含地调用一个异常句柄处理任务

任务管理数据结构

  • 任务状态段TSS
    • 动态字段:当任务切换而被挂起时,处理器会更新动态字段的内容(图中绿色部分)
      • 通用寄存器字段:EAX、ECX、EDX、EBX、ESP、EBP、ESI和EDI
      • 段选择符字段:ES、CS、SS、DS、FS和GS
      • 指令指针EIP字段
      • 先前任务连接字段(后连接字段),允许任务使用iret指令切换到前一个任务
    • 静态字段:处理器会读取静态字段的内容,但通常不会改变它们,任务被创建时设置的(红色部分)
      • LDT段选择符字段
      • CR3控制寄存器字段
      • 特权级0、1和2的栈堆指针字段
      • 调试陷阱T标志字段
      • I/O位图基地址字段

32位任务状态段TSS格式

  • TSS描述符
    • 使用段描述符来定义,且该TSS描述符只能存放在GDT中
    • TYPE字段的忙标志B用于指明任务是否处于忙状态
      • 值为1表示任务正在执行或被挂起
      • 值为0表示任务处于非活动状态

TSS段描述符格式

  • 任务寄存器TR
    • 16位的段选择符+当前任务的TSS段的整个描述符(从GDT中复制而来)
  • 任务门描述符
    • 提供对一个任务间接、受保护的引用
    • 程序可以通过任务门描述符或TSS段描述符来访问一个任务

引用同一任务的任务门

  • 标志寄存器EFLAGS中的NT标志
    • 嵌套任务标志
    • 使用IRET指令从一个任务返回时,处理器会检查并修改这个标志

任务切换

  1. 当前任务对GDT中的TSS描述符执行JMPCALL指令
  2. 当前任务对GDT或LDT中的任务门描述符执行JMPCALL指令
  3. 中断或异常向量指向IDT表中的任务门描述符
  4. 当EFLAGS中的NT标志置位时,当前任务执行IRET指令 任务切换

任务链

  • NT标志指出了当前执行的任务是否嵌套在另一个任务中执行
  • 若软件使用IRET指令挂起新任务
    • NT标志置位,则处理器会切换到前一任务链接字段指定的任务去执行

任务链

  • 当任务切换是由JMP指令造成的,那么新任务就不会是嵌套的(即NT标志为0,且不使用前一任务链接字段)

任务地址空间

  • 由任务能够访问的段构成
把任务映射到线性和物理地址空间
  1. 所有任务共享一个线性到物理地址空间的映射
  2. 每个任务有自己的线性地址空间,并映射到物理地址空间
    • 所有任务的TSS段都必须存放在共享的物理地址空间区域中,并且所有任务都能访问这个区域
任务逻辑地址空间(共享数据)
  • 使用GDT中的段描述符
  • 使用共享的LDT
  • 通过映射到线性地址空间公共区域的不同LDT中的段描述符

8. 保护模式编程初始化

实地址模式

  1. 机器上电或硬件复位,处理器工作在实地址模式下,并从物理地址0xFFFFFFF0处开始执行软件初始化代码
  2. 软件初始化代码必须设置基本系统功能操作必要的数据结构信息

保护模式

  • 保护模式所需要的一些数据结构由处理器内存管理功能确定
  • 在切换到保护模式之前
    • 操作系统加载和初始化软件(bootsect.ssetup.shead.s)必须在内容中先设置号保护模式下使用的数据结构的基本信息
    • IDTGDTTSSLDT、处理器切换到保护模式下运行的代码段、含有中断和异常处理程序的代码模块

9. 多任务内核实例

  • boot.s
    • as86语言编制
    • 用于在计算机系统加电时从启动盘上把内核代码加载到内存中
    • 共512字节
    • 存放在软盘映像文件的第一个扇区
  • head.s
    • GNU as 汇编语言编制
    • 实现了两个运行在特权级3上的任务在时钟中断控制下的相互切换运行
    • 还实现了屏幕上显示字符的一个系统调用
  • 流程
    • PC机加电启动
    • ROM BIOS中的程序会把启动盘上的第一个扇区加载到物理内存0x7c00,并把执行权转移到0x7c00处开始运行boot程序代码
This post is licensed under CC BY 4.0 by the author.