从零到一手写操作系统(四、硬件知识 1)CPU工作模式)
如果追忆会荡起涟漪,那么今天的秋红落叶和晴空万里都归你
https://aeneag.xyz
微信公众号:技术乱舞
艾恩凝
手写操作系统目录
硬件知识
4.1)CPU工作模式
4.1.1)实模式
实模式,又叫实地址模式。运行真实的指令,直接执行指令的真实功能,发向内存的地址是真实的,并且不加限制。
4.1.1.1)实模式寄存器
4.1.1.2)实模式访问内存
代码段CS IP段,栈段SS SP段
4.1.1.3)实模式中断
1、硬件中断,中断控制器发送给CPU一个信号,CPU对信号做出应答,然后中断控制器会将中断号发送给CPU
2、软件中断,CPU执行INT指令 int 21h ,后面是软中断号
4.1.2)保护模式
简言之,就是随着规模不断增加,计算量,内存等需求更大。
内存大了,解决寻址问题,16位只能2的16次方个地址,那么32位的就出现了
另一方面,指令不加区分,对内存访问的地址不加限制
4.1.2.1)保护模式寄存器
4.1.2.2)保护模式特权级
R0-R3,为了区分指令和资源,R0可以执行任何指令,并以此递减。
4.1.2.3)保护模式的段描述符
多个段描述符在内存中形成全局段描述符表,该表的基地址和长度由 CPU 和 GDTR 寄存器指示。如下图所示。
段寄存器中不再存段基地址,而是具体段描述符的索引,访问一个内存地址时,段寄存器中的索引首先会结合 GDTR 寄存器找到内存中的段描述符,再根据其中的段信息判断能不能访问成功。
4.1.3.4)保护模式段选择子
上图是段选择器中的内容,影子寄存器是靠硬件来操作的,对系统程序员不可见,是硬件为了减少性能损耗而设计的一个段描述符的高速缓存,不然每次内存访问都要去内存中查表,那性能损失是巨大的,影子寄存器也正好是 64 位,里面存放了 8 字节段描述符数据。
CPL:当前权限级别
RPL:请求权限级别
DPL:描述符权限级别
看别人的描述怎么就这么晦涩难懂呢,CPL是你的权限,RPL是你要请求的权限级别,一般是CPL=RPL,也可以CPL<RPL,毕竟就四个权限级别,但是如果 CPL > DPL,那么CPU就禁止你访问了,权限不够。
4.1.2.5)保护模式平坦模型
可以看4.1.7中的段描述符,CPU 32 位的寄存器最多只能产生 4GB 大小的地址,而一个段长度也只能是 4GB,所以我们把所有段的基地址设为 0,段的长度设为 0xFFFFF,段长度的粒度设为 4KB,这样所有的段都指向同一个(0~4GB-1)字节大小的地址空间。
1GDT_START:
2knull_dsc: dq 0
3;第一个段描述符CPU硬件规定必须为0
4kcode_dsc: dq 0x00cf9e000000ffff
5;段基地址=0,段长度=0xfffff
6;G=1,D/B=1,L=0,AVL=0
7;P=1,DPL=0,S=1
8;T=1,C=1,R=1,A=0
9kdata_dsc: dq 0x00cf92000000ffff
10;段基地址=0,段长度=0xfffff
11;G=1,D/B=1,L=0,AVL=0
12;P=1,DPL=0,S=1
13;T=0,C=0,R=1,A=0
14GDT_END:
15
16GDT_PTR:
17GDTLEN dw GDT_END-GDT_START-1
18GDTBASE dd GDT_START
如果想真的弄明白,就要仔细读上面的汇编代码。
4.1.2.6)保护模式中断
保护模式中断,不像实模式下,不做权限检查,直接通过中断向量表中的值装载就好了。
保护模式下的中断要权限检查,还有特权级的切换,所以就需要扩展中断向量表的信息,即每个中断用一个中断门描述符来表示,也可以简称为中断门,中断门描述符依然有自己的格式。
产生中断后,CPU 首先会检查中断号是否大于最后一个中断门描述符,x86 CPU 最大支持 256 个中断源(即中断号:0~255),然后检查描述符类型(是否是中断门或者陷阱门)、是否为系统描述符,是不是存在于内存中。
接着,检查中断门描述符中的段选择子指向的段描述符。
最后做权限检查,如果 CPL 小于等于中断门的 DPL,并且 CPL 大于等于中断门中的段选择子所指向的段描述符的 DPL,就指向段描述符的 DPL。(这里权限不够,所以指向DPL)
进一步的,CPL 等于中断门中的段选择子指向段描述符的 DPL,则为同级权限不进行栈切换,否则进行栈切换。如果进行栈切换,还需要从 TSS 中加载具体权限的 SS、ESP,当然也要对 SS 中段选择子指向的段描述符进行检查。(简单说TSS段就是提权时会用到)
做完这一系列检查之后,CPU 才会加载中断门描述符中目标代码段选择子到 CS 寄存器中,把目标代码段偏移加载到 EIP 寄存器中。
注:此处有些知识模糊,TSS段
4.1.2.7)保护模式切换
第一步,准备全局段描述符表
1GDT_START:
2knull_dsc: dq 0
3kcode_dsc: dq 0x00cf9e000000ffff
4kdata_dsc: dq 0x00cf92000000ffff
5GDT_END:
6GDT_PTR:
7GDTLEN dw GDT_END-GDT_START-1
8GDTBASE dd GDT_START
第二步,加载设置 GDTR 寄存器,使之指向全局段描述符表。
1lgdt [GDT_PTR]
第三步,设置 CR0 寄存器,开启保护模式。
1;开启 PE
2mov eax, cr0
3bts eax, 0 ; CR0.PE =1
4mov cr0, eax
第四步,进行长跳转,加载 CS 段寄存器,即段选择子。jmp dword 0x8 :_32bits_mode ;_32bits_mode为32位代码标号即段偏移。
4.1.3)长模式
长模式又名 AMD64,因为这个标准是 AMD 公司最早定义的,它使 CPU 在现有的基础上有了 64 位的处理能力,既能完成 64 位的数据运算,也能寻址 64 位的地址空间。这在大型计算机上犹为重要,因为它们的物理内存通常有几百 GB。
4.1.3.1)长模式寄存器
4.1.3.2)长模式段描述符
当描述符中的 L=1,D/B=0 时,就是 64 位代码段,DPL 还是 0~3 的特权级。然后有多个段描述在内存中形成一个全局段描述符表,同样由 CPU 的 GDTR 寄存器指向。
4.1.3.3)长模式中断
首先为了支持 64 位寻址中断门描述符在原有基础上增加 8 字节,用于存放目标段偏移的高 32 位值。其次,目标代码段选择子对应的代码段描述符必须是 64 位的代码段。最后其中的 IST 是 64 位 TSS 中的 IST 指针,因为我们不使用这个特性,所以不作详细介绍。
长模式也同样在内存中有一个中断门描述符表,只不过表中的条目(如上图所示)是 16 字节大小,最多支持 256 个中断源,对中断的响应和相关权限的检查和保护模式一样,这里不再赘述。
4.1.3.4)长模式切换
第一步,准备长模式全局段描述符表。
1ex64_GDT:
2null_dsc: dq 0
3;第一个段描述符CPU硬件规定必须为0
4c64_dsc:dq 0x0020980000000000 ;64位代码段
5d64_dsc:dq 0x0000920000000000 ;64位数据段
6eGdtLen equ $ - null_dsc ;GDT长度
7eGdtPtr:dw eGdtLen - 1 ;GDT界限
8 dq ex64_GDT
第二步,准备长模式下的 MMU 页表,切换到长模式必须要开启分页
1mov eax, cr4
2bts eax, 5 ;CR4.PAE = 1
3mov cr4, eax ;开启 PAE
4mov eax, PAGE_TLB_BADR ;页表物理地址
5mov cr3, eax
第三步,加载 GDTR 寄存器,使之指向全局段描述表:
1lgdt [eGdtPtr]
第四步,开启长模式,要同时开启保护模式和分页模式,在实现长模式时定义了 MSR 寄存器,需要用专用的指令 rdmsr、wrmsr 进行读写,IA32_EFER 寄存器的地址为 0xC0000080,它的第 8 位决定了是否开启长模式。
1;开启 64位长模式
2mov ecx, IA32_EFER
3rdmsr
4bts eax, 8 ;IA32_EFER.LME =1
5wrmsr
6;开启 保护模式和分页模式
7mov eax, cr0
8bts eax, 0 ;CR0.PE =1
9bts eax, 31
10mov cr0, eax
第五步,进行跳转,加载 CS 段寄存器,刷新其影子寄存器。
1jmp 08:entry64 ;entry64为程序标号即64位偏移地址
切换到长模式和切换保护模式的流程差不多,只是需要准备的段描述符有所区别,还有就是要注意同时开启保护模式和分页模式。
手写操作系统目录
则移山填海之难,
终有成功之日!
——孙文