目录

cpu工作模式

以下主要来自 LMOS 大佬的操作系统课程中记录的笔记

按照 CPU 功能升级迭代的顺序,CPU 的工作模式有:

  • 实模式
  • 保护模式
  • 长模式

实模式

实模式又称实地址模式:

  • 运行真实的指令,不区分直接执行
  • 发往内存的地址是真实的,对任何地址不加限制地发往内存

实模式寄存器

https://img.zhengyua.cn/img/202203130951830.png

实模式下访问内存

https://img.zhengyua.cn/img/202203130952433.png

可以发现所有的内存地址都是:

  • 段寄存器左移 4 位
  • 再加上一个通用寄存器中的值或者常数形成地址
  • 最后由这个地址去访问内存

这就是分段内存管理模型

代码段是由 CS 和 IP 确定的,而栈段是由 SS 和 SP 段确定的。

实模式下的汇编代码,共 16 位:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
data SEGMENT ;定义一个数据段存放Hello World!
    hello  DB 'Hello World!$' ;注意要以$结束
data ENDS
code SEGMENT ;定义一个代码段存放程序指令
    ASSUME CS:CODE,DS:DATA ;告诉汇编程序,DS指向数据段,CS指向代码段
start:
    MOV AX,data  ;将data段首地址赋值给AX                
    MOV DS,AX    ;将AX赋值给DS,使DS指向data段
    LEA DX,hello ;使DX指向hello首地址
    MOV AH,09h   ;给AH设置参数09H,AH是AX高8位,AL是AX低8位,其它类似
    INT 21h      ;执行DOS中断输出DS指向的DX指向的字符串hello
    MOV AX,4C00h ;给AX设置参数4C00h
    INT 21h      ;调用4C00h号功能,结束程序
code ENDS
END start

实模式中断

实模式下的中断实现过程是先保存 CS 和 IP 寄存器,然后装载新的 CS 和 IP 寄存器

中断的产生分为两种:

  • 硬件中断(中断控制器给 CPU 发送了一个电子信号,CPU 会对这个信号作出应答。随后中断控制器会将中断号发送给 CPU)
  • 软件中断( CPU 执行了INT指令,这个指令后面会跟随一个常数,这个常数即是软中断号)

实现中断需要:

  • 内存中放一个中断向量表
  • 中断向量表的地址和长度由 CPU 的特定寄存器 IDTR 指向

实模式下,表中的一个条目由代码段地址和段内偏移组成。CPU 根据 IDTR 通过中断号计算中断向量条目,进而装载 CS(装入代码段基地址)、IP(装入代码段内偏移)寄存器,最终响应中断。

保护模式

为解决实模式中 CPU :

  • 对任何指令都不加区分
  • 对内存的访问都不加限制

基于上述问题 CPU 实现了保护模式。

同时为了解决软件规模增大带来的寻址问题,保护模式下的寄存器和运算单位都要扩展成 32 位

保护模式寄存器

https://img.zhengyua.cn/img/202203131006244.png

保护模式相比于实模式,增加了一些控制寄存器和段寄存器,扩展通用寄存器的位宽,所有的通用寄存器都是 32 位的,还可以单独使用低 16 位,这个低 16 位又可以拆分成两个 8 位寄存器。

保护模式特权级

为区分指令和资源可以被访问,CPU 实现了 特权级。

https://img.zhengyua.cn/img/202203131007908.png

可以看到从外到内各权级之间的联系与区别。如 R0 拥有最大权力,可以访问低特权级的资源。

保护模式段描述符

保护模式下内存还是分段模型,要对内存进行保护,就可以转换成对段的保护

  1. 段描述符

保护模式下 16 位的段寄存器放不下 32位的段基地址和段内偏移,通过借内存空间,把描述一个段的信息封装成特定格式的段描述符,放在内存中

https://img.zhengyua.cn/img/202203131014669.png

  1. 全局描述符

多个段描述符在内存中形成全局段描述符表,该表的基地址和长度由 CPU 和 GDTR 寄存器指示

https://img.zhengyua.cn/img/202203131016813.png

  1. 访问段
  • 保护模式下段寄存器会存放段描述符索引(而不是段基地址)

访问内存地址时,段寄存器中的索引结合 GDTR 寄存器找到内存中的段描述符,再根据其中段信息判断是否能访问成功。

保护模式段选择子

保护模式下的段寄存器存放的有影子寄存器、段描述符索引、描述符表索引、权限级别组成的

  • 影子寄存器:硬件操作,高速缓存提升性能,64位适配段描述符数据
  • 低三位的 TI 和 RPL:对齐段描述符的8字节,每个索引低3位都为0
  • CPL(当前权限级别):通常由 CS 和 SS 中 RPL 组成,当 CPL 大于目标段 DPL 则禁止访问

https://img.zhengyua.cn/img/202203131021155.png

保护模式平坦模型

分段模型有很多权限,现代操作系统大多使用分页模型。

在 x86 CPU 中不能直接使用分页模型,需要在分段模型的前提下,根据需要决定是否要开启分页。因为硬件的规定,我们只能简化设计,来使分段成为一种“虚设”,即保护模式的平坦模型

CPU 32 位的寄存器最多只能产生 4GB 大小的地址,而一个段长度也只能是 4GB,所以我们把:

  • 所有段的基地址设为 0
  • 段的长度设为 0xFFFFF
  • 段长度的粒度设为 4KB

这样所有的段都指向同一个(0~4GB-1)字节大小的地址空间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

GDT_START:
knull_dsc: dq 0
;第一个段描述符CPU硬件规定必须为0
kcode_dsc: dq 0x00cf9e000000ffff
;段基地址=0,段长度=0xfffff
;G=1,D/B=1,L=0,AVL=0 
;P=1,DPL=0,S=1
;T=1,C=1,R=1,A=0
;(段基址31:24) 0000 0000
;(G、D/B、L、AVL)1100
;(段长度19:16)1111
;(P、DPL、DPL、S、T、C、R、A)1001 1110
;(段基址23:0)0000 0000 0000 0000 0000 0000
;(段长度15:0)1111 1111 1111 1111
kdata_dsc: dq 0x00cf92000000ffff
;段基地址=0,段长度=0xfffff
;G=1,D/B=1,L=0,AVL=0 
;P=1,DPL=0,S=1
;T=0,C=0,R=1,A=0
GDT_END:

GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1
GDTBASE  dd GDT_START

保护模式中断

  • 实模式下 CPU 不需要做权限检查,所以可直接通过中断向量表中的值装载 CS:IP 寄存器
  • 保护模式下的中断需要权限检查和特权级的切换,所以需要扩展中断向量表的信息,即每个中断用一个中断门描述符来表示(简称为中断门)

https://img.zhengyua.cn/img/202203131042594.png

保护模式下中断实现与实模式大致相同,区别在于条目换成了中断门描述符。其产生中断的主要过程如下:

  • 检查中断门描述符
  • 检查中断门描述符中的段选择子指向的段描述符
  • 权限检查
    • 若 CPL <= 中断门的 DPL(有权限能执行中断)
    • 且 CPL >= 中断门中的段选择子所指向的段描述符的 DPL(能够进入相应的服务程序),就指向段描述符的 DPL(此时 CPL 等于 DPL)
    • 若 CPL > 中断门中的段选择子指向的段描述符的 DPL,则进行栈切换(需要从 TSS 中加载具体权限的 SS、ESP,也要对 SS 中段选择子指向的段描述符进行检查)
  • 检查后加载中断门描述符中:
    • 目标代码段选择子到 CS 寄存器中
    • 目标代码段偏移加载到 EIP 寄存器中

https://img.zhengyua.cn/img/202203131044009.png

切换到保护模式

x86 CPU 在第一次加电和每次 reset 后,都会自动进入实模式,若要进入保护模式,则需要通过代码实现切换,其步骤如下:

  1. 准备全局段描述符表
1
2
3
4
5
6
7
8
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
GDT_END:
GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1
GDTBASE  dd GDT_START
  1. 加载设置 GDTR 寄存器,使之指向全局段描述符表
1
lgdt [GDT_PTR]
  1. 设置 CR0 寄存器,开启保护模式
1
2
3
4
5
6
7
8
;开启 PE
mov eax, cr0
bts eax, 0                      
;CR0.PE =1
;bts指令的意思是 bit test and set 位测试并设置
;判断 eax 与0,若 eax == 0,bts 会将 CF = 1,并将eax置位(即设置为1)
;然后把 CR0 的最低位设为1之后,即开启了保护模式
mov cr0, eax         
  1. 进行长跳转,加载 CS 段寄存器,即段选择子

长跳转的目的在于无法直接或间接 mov 一个数据到 CS 寄存器中,因为刚刚开启保护模式时,CS 的影子寄存器还是实模式下的值,所以需要告诉 CPU 加载新的段信息。

1
2
jmp dword 0x8 :_32bits_mode
;_32bits_mode为32位代码标号即段偏移

这里填入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的内存地址 = 段基址 + 段内偏移地址

  1. 进入保护模式
  • CPU 发现了 CRO 寄存器第 0 位的值是 1
  • 按 GDTR 的指示找到全局描述符表
  • 然后根据索引值 8,进行一系列合法检查
  • 把新的段描述符信息加载到 CS 影子寄存器

长模式

长模式又名 AMD64(该标准是 AMD 公司最早定义的),它使 CPU 在现有的基础上有了 64 位的处理能力(完成 64 位数据运算和寻址 64 位的地址空间)。

长模式寄存器

长模式相比于保护模式,增加了一些通用寄存器,并扩展通用寄存器的位宽,所有的通用寄存器都是 64 位,还可以单独使用低 32 位。

https://img.zhengyua.cn/img/202203131113440.png

长模式段描述符

与保护模式绝大多数特性相同,如特权级和权限检查。

长模式下的段描述符格式如下:

https://img.zhengyua.cn/img/202203131114066.png

在长模式下,弱化段模式管理,CPU 不再对段基址和段长度进行检查(地址检查交给 MMU),只对 DPL 进行相关的检查。同样也有全局段描述符,且由 CPU 的 GDTR 寄存器指向。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ex64_GDT:
null_dsc:  dq 0
;第一个段描述符CPU硬件规定必须为0
c64_dsc:dq 0x0020980000000000  ;64位代码段
;无效位填0
;D/B=0,L=1,AVL=0 
;P=1,DPL=0,S=1
;T=1,C=0,R=0,A=0
d64_dsc:dq 0x0000920000000000  ;64位数据段
;无效位填0
;P=1,DPL=0,S=1
;T=0,C/E=0,R/W=1,A=0
eGdtLen   equ $ - null_dsc  ;GDT长度
eGdtPtr:dw eGdtLen - 1  ;GDT界限
     dq ex64_GDT

段描述符的 DPL=0,这说明需要最高权限即 CPL=0 才能访问。 若是数据段的话,G、D/B、L 位都是无效的。

长模式中断

长模式的中断与保护模式的中断过程大致相同,主要区别在于中断门描述符格式为适应64位进行了修改和扩展:

  • 在原有基础上增加 8 字节,用于存放目标段偏移的高 32 位值(支持 64 位寻址)
  • 目标代码段选择子对应的代码段描述符必须是 64 位的代码段
  • IST 是 64 位 TSS 中的 IST 指针

https://img.zhengyua.cn/img/202203131120421.png

切换到长模式

切换到长模式的步骤如下:

  1. 准备长模式全局段描述符表
1
2
3
4
5
6
7
8
9

ex64_GDT:
null_dsc:  dq 0
;第一个段描述符CPU硬件规定必须为0
c64_dsc:dq 0x0020980000000000  ;64位代码段
d64_dsc:dq 0x0000920000000000  ;64位数据段
eGdtLen   equ $ - null_dsc  ;GDT长度
eGdtPtr:dw eGdtLen - 1  ;GDT界限
     dq ex64_GDT
  1. 准备 MMU 页表开启分页
1
2
3
4
5
6

mov eax, cr4
bts eax, 5   ;CR4.PAE = 1
mov cr4, eax ;开启 PAE
mov eax, PAGE_TLB_BADR ;页表物理地址
mov cr3, eax

长模式下内存地址空间的保护交给了 MMU,MMU 依赖页表对地址进行转换,页表有特定的格式存放在内存中,其地址由 CPU 的 CR3 寄存器指向

  1. 加载 GDTR 寄存器,使之指向全局段描述表
1
lgdt [eGdtPtr]
  1. 开启长模式同时开启保护模式和分页模式
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
;开启 64位长模式
mov ecx, IA32_EFER
rdmsr
bts eax, 8  ;IA32_EFER.LME =1
wrmsr
;开启 保护模式和分页模式
mov eax, cr0
bts eax, 0    ;CR0.PE =1
bts eax, 31
mov cr0, eax 

在实现长模式时定义了 MSR 寄存器,需要用专用的指令 rdmsr、wrmsr 进行读写,IA32_EFER 寄存器的地址为 0xC0000080,它的第 8 位决定了是否开启长模式。

  1. 进行跳转,加载 CS 段寄存器,刷新其影子寄存器
1
jmp 08:entry64 ;entry64为程序标号即64位偏移地址

参考

https://time.geekbang.org/column/intro/100078401?tab=catalog