cpu工作模式
以下主要来自 LMOS 大佬的操作系统课程中记录的笔记
按照 CPU 功能升级迭代的顺序,CPU 的工作模式有:
- 实模式
- 保护模式
- 长模式
实模式
实模式又称实地址模式:
- 运行真实的指令,不区分直接执行
- 发往内存的地址是真实的,对任何地址不加限制地发往内存
实模式寄存器
实模式下访问内存
可以发现所有的内存地址都是:
- 段寄存器左移 4 位
- 再加上一个通用寄存器中的值或者常数形成地址
- 最后由这个地址去访问内存
这就是分段内存管理模型。
代码段是由 CS 和 IP 确定的,而栈段是由 SS 和 SP 段确定的。
实模式下的汇编代码,共 16 位:
|
|
实模式中断
实模式下的中断实现过程是先保存 CS 和 IP 寄存器,然后装载新的 CS 和 IP 寄存器。
中断的产生分为两种:
- 硬件中断(中断控制器给 CPU 发送了一个电子信号,CPU 会对这个信号作出应答。随后中断控制器会将中断号发送给 CPU)
- 软件中断( CPU 执行了
INT
指令,这个指令后面会跟随一个常数,这个常数即是软中断号)
实现中断需要:
- 内存中放一个中断向量表
- 中断向量表的地址和长度由 CPU 的特定寄存器 IDTR 指向
实模式下,表中的一个条目由代码段地址和段内偏移组成。CPU 根据 IDTR 通过中断号计算中断向量条目,进而装载 CS(装入代码段基地址)、IP(装入代码段内偏移)寄存器,最终响应中断。
保护模式
为解决实模式中 CPU :
- 对任何指令都不加区分
- 对内存的访问都不加限制
基于上述问题 CPU 实现了保护模式。
同时为了解决软件规模增大带来的寻址问题,保护模式下的寄存器和运算单位都要扩展成 32 位。
保护模式寄存器
保护模式相比于实模式,增加了一些控制寄存器和段寄存器,扩展通用寄存器的位宽,所有的通用寄存器都是 32 位的,还可以单独使用低 16 位,这个低 16 位又可以拆分成两个 8 位寄存器。
保护模式特权级
为区分指令和资源可以被访问,CPU 实现了 特权级。
可以看到从外到内各权级之间的联系与区别。如 R0 拥有最大权力,可以访问低特权级的资源。
保护模式段描述符
保护模式下内存还是分段模型,要对内存进行保护,就可以转换成对段的保护。
- 段描述符
保护模式下 16 位的段寄存器放不下 32位的段基地址和段内偏移,通过借内存空间,把描述一个段的信息封装成特定格式的段描述符,放在内存中。
- 全局描述符
多个段描述符在内存中形成全局段描述符表,该表的基地址和长度由 CPU 和 GDTR 寄存器指示。
- 访问段
- 保护模式下段寄存器会存放段描述符索引(而不是段基地址)
访问内存地址时,段寄存器中的索引结合 GDTR 寄存器找到内存中的段描述符,再根据其中段信息判断是否能访问成功。
保护模式段选择子
保护模式下的段寄存器存放的有影子寄存器、段描述符索引、描述符表索引、权限级别组成的:
- 影子寄存器:硬件操作,高速缓存提升性能,64位适配段描述符数据
- 低三位的 TI 和 RPL:对齐段描述符的8字节,每个索引低3位都为0
- CPL(当前权限级别):通常由 CS 和 SS 中 RPL 组成,当 CPL 大于目标段 DPL 则禁止访问
保护模式平坦模型
分段模型有很多权限,现代操作系统大多使用分页模型。
在 x86 CPU 中不能直接使用分页模型,需要在分段模型的前提下,根据需要决定是否要开启分页。因为硬件的规定,我们只能简化设计,来使分段成为一种“虚设”,即保护模式的平坦模型。
CPU 32 位的寄存器最多只能产生 4GB 大小的地址,而一个段长度也只能是 4GB,所以我们把:
- 所有段的基地址设为 0
- 段的长度设为 0xFFFFF
- 段长度的粒度设为 4KB
这样所有的段都指向同一个(0~4GB-1)字节大小的地址空间。
|
|
保护模式中断
- 实模式下 CPU 不需要做权限检查,所以可直接通过中断向量表中的值装载 CS:IP 寄存器
- 保护模式下的中断需要权限检查和特权级的切换,所以需要扩展中断向量表的信息,即每个中断用一个中断门描述符来表示(简称为中断门)
保护模式下中断实现与实模式大致相同,区别在于条目换成了中断门描述符。其产生中断的主要过程如下:
- 检查中断门描述符
- 检查中断门描述符中的段选择子指向的段描述符
- 权限检查:
- 若 CPL <= 中断门的 DPL(有权限能执行中断)
- 且 CPL >= 中断门中的段选择子所指向的段描述符的 DPL(能够进入相应的服务程序),就指向段描述符的 DPL(此时 CPL 等于 DPL)
- 若 CPL > 中断门中的段选择子指向的段描述符的 DPL,则进行栈切换(需要从 TSS 中加载具体权限的 SS、ESP,也要对 SS 中段选择子指向的段描述符进行检查)
- 检查后加载中断门描述符中:
- 目标代码段选择子到 CS 寄存器中
- 目标代码段偏移加载到 EIP 寄存器中
切换到保护模式
x86 CPU 在第一次加电和每次 reset 后,都会自动进入实模式,若要进入保护模式,则需要通过代码实现切换,其步骤如下:
- 准备全局段描述符表
|
|
- 加载设置 GDTR 寄存器,使之指向全局段描述符表
|
|
- 设置 CR0 寄存器,开启保护模式
|
|
- 进行长跳转,加载 CS 段寄存器,即段选择子
长跳转的目的在于无法直接或间接 mov 一个数据到 CS 寄存器中,因为刚刚开启保护模式时,CS 的影子寄存器还是实模式下的值,所以需要告诉 CPU 加载新的段信息。
|
|
这里填入CS的值是0x8,根据段选择子的格式定义:
0x8: INDEX TI CPL 0000 0000 1 00 0
- INDEX代表GDT中的索引
- TI代表使用GDTR中的GDT
- CPL代表处于特权级,即GDT表中的第1个(index从0开始)描述符
_32bits_mode作为段内偏移,然后得 GDT第一个1段描述符 + _32bits_mode的内存地址 = 段基址 + 段内偏移地址
- 进入保护模式
- CPU 发现了 CRO 寄存器第 0 位的值是 1
- 按 GDTR 的指示找到全局描述符表
- 然后根据索引值 8,进行一系列合法检查
- 把新的段描述符信息加载到 CS 影子寄存器
长模式
长模式又名 AMD64(该标准是 AMD 公司最早定义的),它使 CPU 在现有的基础上有了 64 位的处理能力(完成 64 位数据运算和寻址 64 位的地址空间)。
长模式寄存器
长模式相比于保护模式,增加了一些通用寄存器,并扩展通用寄存器的位宽,所有的通用寄存器都是 64 位,还可以单独使用低 32 位。
长模式段描述符
与保护模式绝大多数特性相同,如特权级和权限检查。
长模式下的段描述符格式如下:
在长模式下,弱化段模式管理,CPU 不再对段基址和段长度进行检查(地址检查交给 MMU),只对 DPL 进行相关的检查。同样也有全局段描述符,且由 CPU 的 GDTR 寄存器指向。
|
|
段描述符的 DPL=0,这说明需要最高权限即 CPL=0 才能访问。 若是数据段的话,G、D/B、L 位都是无效的。
长模式中断
长模式的中断与保护模式的中断过程大致相同,主要区别在于中断门描述符格式为适应64位进行了修改和扩展:
- 在原有基础上增加 8 字节,用于存放目标段偏移的高 32 位值(支持 64 位寻址)
- 目标代码段选择子对应的代码段描述符必须是 64 位的代码段
- IST 是 64 位 TSS 中的 IST 指针
切换到长模式
切换到长模式的步骤如下:
- 准备长模式全局段描述符表
|
|
- 准备 MMU 页表开启分页
|
|
长模式下内存地址空间的保护交给了 MMU,MMU 依赖页表对地址进行转换,页表有特定的格式存放在内存中,其地址由 CPU 的 CR3 寄存器指向
- 加载 GDTR 寄存器,使之指向全局段描述表
|
|
- 开启长模式同时开启保护模式和分页模式
|
|
在实现长模式时定义了 MSR 寄存器,需要用专用的指令 rdmsr、wrmsr 进行读写,IA32_EFER 寄存器的地址为 0xC0000080,它的第 8 位决定了是否开启长模式。
- 进行跳转,加载 CS 段寄存器,刷新其影子寄存器
|
|
参考
https://time.geekbang.org/column/intro/100078401?tab=catalog