1.实模式
实模式两个特点:
- 实模式会不加区分地执行指令的真实功能。
- 实模式发往内存的地址是真实的,对任何地址不加限制地发往内存。
1.1实模式寄存器
下方为x86 CPU在实模式下的寄存器。
1.2实模式下访问内存
访问内存包括取值操作和访问内存数据操作,这些都需要通过地址值获取。
所有的内存地址都是由段寄存器左移 4 位,再加上一个通用寄存器中的值或者常数形成地址,然后由这个地址去访问内存。这个方式就是分段内存管理模型。
为什么段寄存器要左移四位:因为早期CPU对应的地址总线有20位,理论上可以访问的内存空间有220,但是段寄存器只有16位,所以将它左移四位,配合偏移寄存器就可以表示1M的内存了。
因此有:地址 = CS(段寄存器) * 16 + IP(程序指针寄存器)
特别注意的是,代码段是由 CS 和 IP 确定的,而栈段是由 SS 和 SP 段确定的。
1.3实模式中断
中断即中止执行当前程序,转而跳转到另一个特定的地址上,去运行特定的代码。在实模式下它的实现过程是先保存 CS 和 IP 寄存器,然后装载新的 CS 和 IP 寄存器。
中断有两种情况:
- 硬件中断:中断控制器给 CPU 发送了一个电子信号,CPU 会对这个信号作出应答。随后中断控制器会将中断号发送给 CPU。
- 软件中断:CPU 执行了 INT 指令,这个指令后面会跟随一个常数,这个常数即是软中断号。
为了实现实模式下的中断,就在内存中存放了一个中断向量表,该表地址和长度由寄存器IDTR指向,实模式下,表中的一个条目由代码段地址和段内偏移组成。
通过这种方式将需要的段基地址和偏移地址装载到CS和IP寄存器中,响应中断。
2.保护模式
内存变大,首先要解决的问题是寻址问题,因为 16 位的寄存器最多只能表示 216 个地址,所以 CPU 的寄存器和运算单元都要扩展成 32 位的。其次要解决实模式对指令不加区分,地址不加限制的问题。
基于上述问题,CPU实现了保护模式。
2.1保护模式寄存器
注意段寄存器仍然是16位。
2.2保护模式特权级
特权级分为 4 级,R0~R3,每个特权级执行指令的数量不同,R0 可以执行所有指令,R1、R2、R3 依次递减,它们只能执行上一级指令数量的子集。(R0>R1>R2>R3)
除此之外,特权级还能体现各特权级对资源控制访问的多少和各特权级之间的包含关系。R0 拥有最大权力,可以访问低特权级的资源,反之则不行。
2.3保护模式段描述符
此时内存还是分段模型,要对内存进行保护,就可以转换成对段的保护。
CPU扩展导致16位的段寄存器无法存放指令和数据地址。于是描述一个段的信息封装成特定格式的段描述符,放在内存中。
多个段描述符在内存中形成全局段描述符表,该表的基地址和长度由 CPU 中的 GDTR 寄存器指示。
段寄存器中存放具体段描述符的索引。
2.4保护模式段选择子
段寄存器是由影子寄存器、段描述符索引、描述符表索引、权限级别组成的。
影子寄存器是靠硬件来操作的,对系统程序员不可见,是硬件为了减少性能损耗而设计的一个段描述符的高速缓存(类似cache)。其长度64位,内部存放8字节的段描述符数据。
低三位之所以能放 TI 和 RPL,是因为段描述符 8 字节对齐,索引一定是8字节的整数倍,换算下来第三位永远是0,可以存放额外的信息。
通常情况下,CS 和 SS 中 RPL 就组成了 CPL(当前权限级别),所以常常是 RPL=CPL,进而 CPL 就表示发起访问者要以什么权限去访问目标段,当 CPL 大于目标段 DPL 时,则 CPU 禁止访问,只有 CPL 小于等于目标段 DPL 时才能访问。
2.5保护模式平坦模型
现代操作系统都会使用分页模型而非分段模型,但是 x86 CPU 并不能直接使用分页模型,而是要在分段模型的前提下,根据需要决定是否要开启分页。因为这是硬件的规定。
但是我们可以简化设计,来使分段成为一种“虚设”,这就是保护模式的平坦模型。
CPU32 位的寄存器最多只能产生 4GB 大小的地址,而一个段长度也只能是 4GB,所以我们把所有段的基地址设为 0,段的长度设为 0xFFFFF,段长度的粒度设为 4KB,这样所有的段都指向同一个((段的长度 +1)* 粒度 - 1)字节大小的地址空间。
也就是任何分段都指向一个大小为4GB的空间,对任意一个段内的偏移地址的访问等同于对所有段内偏移地址的访问,也就等同于没有分段了。
在此基础上搓一个保护模式的段描述符表
1 | GDT_START: |
2.6保护模式中断
保护模式下的中断要权限检查,还有特权级的切换,所以就需要扩展中断向量表的信息,即每个中断用一个中断门描述符来表示,也可以简称为中断门。
保护模式要实现中断,也必须在内存中有一个中断向量表,也是由 IDTR 寄存器指向,只不过中断向量表中的条目变成了中断门描述符。
产生中断后,CPU 首先会检查中断号是否大于最后一个中断门描述符,x86 CPU 最大支持 256 个中断源(即中断号:0~255),然后检查描述符类型(是否是中断门或者陷阱门)、是否为系统描述符,是不是存在于内存中。接着,检查中断门描述符中的段选择子指向的段描述符。
最后做权限检查,如果 CPL 小于等于中断门的 DPL,并且 CPL 大于等于中断门中的段选择子所指向的段描述符的 DPL,就指向段描述符的 DPL(进不去指向的内存地址)。
进一步的,CPL 等于中断门中的段选择子指向段描述符的 DPL,则为同级权限不进行栈切换,否则进行栈切换。如果进行栈切换,还需要从 TSS 中加载具体权限的 SS、ESP,当然也要对 SS 中段选择子指向的段描述符进行检查。
做完这一系列检查之后,CPU 才会加载中断门描述符中目标代码段选择子到 CS 寄存器中,把目标代码段偏移加载到 EIP 寄存器中。
例:当前运行代码CPL=R3级别,遇R3中断门进门,执行特权级中断程序R0,此时CRL=R0。
3.长模式
它使 CPU 在现有的基础上有了 64 位的处理能力,既能完成 64 位的数据运算,也能寻址 64 位的地址空间。
3.1长模式寄存器
长模式相比于保护模式,增加了一些通用寄存器,并扩展通用寄存器的位宽,所有的通用寄存器都是 64 位。
3.2长模式段描述符
长模式依然具备保护模式绝大多数特性,如特权级和权限检查。在长模式下,CPU 不再对段基址和段长度进行检查,只对 DPL 进行相关的检查,这个检查流程和保护模式下一样。
当描述符中的 L=1,D/B=0 时,就是 64 位代码段,DPL 还是 0~3 的特权级。然后有多个段描述在内存中形成一个全局段描述符表,同样由 CPU 的 GDTR 寄存器指向。
搓一个长模式下的段描述符表
1 | ex64_GDT: |
上述代码段描述符的 DPL=0,这说明需要最高权限即 CPL=0 才能访问。若是数据段的话,G、D/B、L 位都是无效的。
3.3长模式中断
长模式支持 64 位内存寻址,所以要对中断门描述符进行修改和扩展。
首先为了支持 64 位寻址中断门描述符在原有基础上增加 8 字节,用于存放目标段偏移的高 32 位值。其次,目标代码段选择子对应的代码段描述符必须是 64 位的代码段。最后其中的 IST 是 64 位 TSS 中的 IST 指针。
长模式也同样在内存中有一个中断门描述符表,只不过表中的条目(如上图所示)是 16 字节大小,最多支持 256 个中断源,对中断的响应和相关权限的检查和保护模式一样。