(2021年11月17日打卡第十天)
打卡第十天:05 | CPU工作模式:执行程序的三种模式
05 | CPU工作模式:执行程序的三种模式
学习本节,了解CPU的三种工作模式。
1、实模式工作原理与实模式中断
实模式又称实地址模式,实,即真实,这个真实分为两个方面,一个方面是运行真实的指令,对指令的动作不作区分,直接执行指令的真实功能,另一方面是发往内存的地址是真实的,对任何地址不加限制地发往内存。
- 结合上图可以发现,所有的内存地址都是由段寄存器左移 4 位,再加上一个通用寄存器中的值或者常数形成地址,然后由这个地址去访问内存。这就是大名鼎鼎的分段内存管理模型。
- 代码段是由 CS 和 IP 确定的,而栈段是由 SS 和 SP 段确定的。
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中断输出DX指向的字符串hello
MOV AX,4C00h ;给AX设置参数4C00h
INT 21h ;调用4C00h号功能,结束程序
code ENDS
END start
-
上述代码中的结构模型,也是符合 CPU 实模式下分段内存管理模式的,它们被汇编器转换成二进制数据后,也是以段的形式存在的。
-
中断即中止执行当前程序,转而跳转到另一个特定的地址上,去运行特定的代码。
在实模式下它的实现过程是先保存 CS 和 IP 寄存器,然后装载新的 CS 和 IP 寄存器。 -
中断分为硬件中断和软件中断。
2、保护模式工作原理、特权级
- 解决寻址问题:16 位的寄存器最多只能表示
2
16
2^{16}
216个地址
(也就是64KB)
,所以 CPU 的寄存器和运算单元都要扩展成 32 位的($2^{32}$也就是4GB)
。 - 解决指令保护问题:CPU 实现了特权级,特权级分为 4 级,R0~R3,每个特权级执行指令的数量不同。
- 解决内存保护问题:段机制和页机制。
- 我们一眼就可以看出,段寄存器中不再存放段基地址,而是具体段描述符的索引,访问一个内存地址时,段寄存器中的索引首先会结合 GDTR 寄存器找到内存中的段描述符,再根据其中的段信息判断能不能访问成功。
- CS、DS、ES、SS、FS、GS 这些段寄存器,它们是由影子寄存器、段描述符索引(占13位)、描述符表索引TI、权限级别RPL组成的。
- 通常情况下,CS 和 SS 中 RPL 就组成了 CPL(当前权限级别),所以常常是 RPL=CPL,进而 CPL 就表示发起访问者要以什么权限去访问目标段,当 CPL 在数值上大于目标段 DPL 时(例如CPL=3,DPL=0),则 CPU 禁止访问,只有 CPL 在数值上小于等于目标段 DPL 时才能访问(例如CPL=0,DPL=0或者3)。
- 我们可以简化设计,来使分段成为一种“虚设”,这就是保护模式的平坦模型。我们把所有段的基地址设为 0,段的长度设为 0xFFFFF1
($2^{20}$=1MB,段长度的粒度=4KB,所以一个段的长度为1MB*4KB=4GB)
,段长度的粒度设为 4KB,这样所有的段都指向同一个(0~4GB-1)字节大小的地址空间。
下面我们还是看一看前面 Hello OS 中段描述符表,如下所示。
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
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
;第1行和第5行之间有3个段描述符,5-1=4,所以再减1就等于3,也就是这3个段描述符
GDTBASE dd GDT_START
上面代码中注释已经很明白了,段长度需要和 G 位配合,若 G 位为 1 则段长度等于 0xfffff 个 4KB。上面段描述符的 DPL=0,这说明需要最高权限即 CPL=0 才能访问。
3、长模式工作原理、长模式切换
- 长模式又名 AMD64,在长模式下,CPU 不再对段基址和段长度进行检查,只对 DPL 进行相关的检查,这个检查流程和保护模式下一样。
- 下面我们来写一个长模式下的段描述符表,加深一下理解,如下所示:
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
上面代码中注释已经很清楚了,段长度和段基址都是无效的填充为 0,CPU 不做检查。但是上面段描述符的 DPL=0,这说明需要最高权限即 CPL=0 才能访问。若是数据段的话,G、D/B、L 位都是无效的。
我们既可以从实模式直接切换到长模式,也可以从保护模式切换长模式。切换到长模式的步骤如下。
- 准备长模式全局段描述符表:
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
- 准备长模式下的 MMU 页表:
mov eax, cr4
bts eax, 5 ;CR4.PAE = 1
mov cr4, eax ;开启 PAE
mov eax, PAGE_TLB_BADR ;页表物理地址
mov cr3, eax
- 加载 GDTR 寄存器,使之指向全局段描述表:
lgdt [eGdtPtr]
- 开启长模式,要同时开启保护模式和分页模式:
;开启 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 ;CR0.PG =1
mov cr0, eax
- 进行跳转,加载 CS 段寄存器,刷新其影子寄存器。
jmp 08:entry64 ;entry64为程序标号即64位偏移地址
上述BTS指令的作用:
//两件事:
一:
判断ecx的值:
IF ecx == 0 则 CF = 1
IF ecx != 0 则 CF = 0
二:
lock bts dword ptr [ecx],0 //将dword ptr [ecx]指向的内存地址的第0位置1
lock bts dword ptr [ecx],1 //将dword ptr [ecx]指向的内存地址的第1位置1