保护模式在 Intel 80286 CPU 中首次出现。
实模式的缺陷:
实模式下操作系统和用户程序属于同一特权级逻辑地址等于物理地址用户程序可以自由修改段基址,访问所有内存访问超过 64KB 的内存区域时要切换段基址一次只能运行一个程序,无法充分利用计算机资源共 20 条地址线,最大可用内存为 1MB保护模式下,物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)需要被转化为物理地址后再去访问。
地址转换由处理器和操作系统共同协作完成。
实模式的 CPU 运行环境 16 位,保护模式的运行环境是 32 位。
保护模式下,除段寄存器外,通用寄存器、指令指针寄存器、标志寄存器都由原来的 16 位扩展到了 32 位。
全局描述符表中至少有一个表项,每一个表项称为段描述符,其大小为 64 字节,用来描述各个内存段的起始地址、大小、权限等信息。
全局描述符表很大,放在内存中,由 GDTR 寄存器指向它。
保护模式下,段寄存器中保存的是“选择子“。”选择子“是一个数,用这个数来索引全局描述符表中的段描述符,就像数组下标。
在 80286 的保护模式中,使用了段描述符缓冲寄存器。只要往段寄存器中赋值,CPU 就会更新段描述符缓冲寄存器。
80386 是首款 32 位处理器,它的地址总线和寄存器都是 32 位的。
在保护模式下,内存寻址中,基址寄存器不再只是 bx、bp,而是所有 32 位的通用寄存器;变址寄存器不再只是 si、di,而是除 esp 之外的所有 32 位通用寄存器,偏移量由实模式下的 16 位变成了 32 位。
另外,可以对变址寄存器乘以一个比例因子,比例因子只能是1、2、4、8。
虽然 esp 无法用做变址寄存器,但其可用于基址寄存器。
bits 指令:
[bits 16] 是告诉编译器,下面的代码帮我编译成 16 位机器码[bits 32] 是告诉编译器,下面的代码帮我编译成 32 位机器码未使用 bits 指令的地方,默认是 [bits 16]。
指令格式中,有一个“前缀”字段。存放指令重复前缀 rep、段跨越前缀“段寄存器:"、操作数反转前缀 0x66 和寻址方式反转前缀 0x67。
在指令(机器码)中添加了 0x66 反转前缀之后:
假设当前运行模式是 16 位实模式,操作数大小将变为 32 位假设当前运行模式是 32 位实模式,操作数大小将变为 16 位寻址方式反转前缀 0x67。
mul 指令:
如果乘数是 8 位,则把寄存器 al 当作另一个乘数,结果便是 16 位,存入寄存器 ax如果乘数是 16 位,则把寄存器 ax 当作另一个乘数,结果便是 32 位,存入寄存器 eax如果乘数是 32 位,则把寄存器 eax 当作另一个乘数,结果便是 64 位,存入 edx: eax,其中 edx 是积的高 32 位,eax 是积的低 32 位有符号相乘指令 imul 也是一样。
div 指令:
如果除数是 8 位,被除数就是 16 位,位于寄存器 ax。所得的结果,商在寄存器 al,余数在寄存器 ah如果除数是 16 位,被除数就是 32 位,被除数的高 16 位位于寄存器 dx,低 16 位位于寄存器 ax。所得的结果,商在寄存器 ax,余数在寄存器 dx如果除数是 32 位,被除数是 64 位,被除数的高 32 位则位于寄存器 edx,被除数的低 32 位则位于寄存器 eax,所得的结果,商在寄存器 eax,余数在寄存器 edxpush 指令:
当压入 8 位立即数时,CPU 将其扩展为 4 字节后入栈当压入 16 位立即数时,CPU 直接压入 2 字节当压入 32 位立即数时,CPU 直接压入 4 字节全局描述符表(GDT)是保护模式下内存段的登记表,这是不同于实模式的显著特征之一。
段界限表示段边界的扩展最值,即最大扩展到多少或最小扩展到多少。
扩展方向只有上下两种:
对于数据段和代码段,段的扩展方向是向上对于栈段,扩展方向是向下段界限用 20 个二进制位来表示,是个单位量,单位要么是字节,要么是 4KB,这是由描述符中的 G 位来指定的。
段界限边界值 = (描述符中段界限 + 1)*(段界限的粒度大小:4KB 或者 1)- 1
如果 G 位为 0,表示段界限粒度大小为 1 字节。如果 G 位为 1,表示段界限粒度大小为 4KB 字节。
type 字段共 4 位,用来指定本描述符的类型。
S 位决定是否是系统段,S 为 0 时表示系统段,S 为 1 时表示数据段。一个段描述符在 CPU 眼里分为两大类,要么描述的是系统段,要么描述的是数据段。
在 CPU 眼里,凡是硬件运行需要用到的东西都可称之为系统,凡是软件需要的东西都成为数据。
表中的 A 位表示 Accessed 位,由 CPU 来设置,每当该段被 CPU 访问过后,CPU 将就此位置 1。创建一个新段描述符时,应该将此位置 0。
C 表示一致性代码段。C 为 1 时表示该段是一致性代码段,C 为 0 时表示该段为非一致性代码段。
R 表示可读,R 为 1 表示可读,R 为 0 表示不可读。不可读的代码段只是用来限制代码指令的,并不是连 CPU 也不能看。
X 表示该段是否可执行。X 为 1 表示可执行,X 为 0 表示不可执行。
E 用来标识段的扩展方向。E 为 0 表示向上扩展,E 为 1 表示向下扩展。
W 指段是否可写。W 为 1 表示可写,W 为 0 表示不可写入。
DPL (Descriptor Provilege Level)字段,即描述符特权级。能表示 4 种特权级,分别是 0、1、2、3 级特权,数字越小,特权级越大。
CPU 由实模式进入保护模式后,特权级自动为 0。某些指令只能在 0 特权级下执行,从而保证安全。
P 字段表示段是否存在。如果段存在于内存中,P 为 1,否则 P 为 0。
AVL 字段没有专门的用途,操作系统可以随意使用此位。
L 字段用来设置是否是 64 位代码段。L 为 1 表示 64 位代码段,否则表示 32 位代码段。
D/B 字段用来指示有效地址(段内偏移地址)及操作数的大小:
对于代码段来说,此位是 D 位,若 D 为 0,表示指令中的有效地址和操作数是 16 位,指令有效地址用 IP 寄存器。若 D 为 1,表示指令中的有效地址及操作数是 32 位,指令有效地址用 EIP 寄存器对于栈段来说,此位是 B 位。若 B 为 0,使用的是 sp 寄存器,栈的起始地址是 16 位寄存器的最大寻址范围,0xFFFF。若 B 为 1,使用的是 esp 寄存器,栈的起始地址是 0xFFFFFFFFG 字段用来指定段界限的单位大小。若 G 为 0,表示段界限的单位是 1 字节。若 G 为 1,表示段界限的单位是 4KB。
全局描述符表 GDT 相当于是描述符的数组,数组中每个元素都是 8 字节的描述符,可以用选择子中提供的下标在 GDT 中索引描述符。
GDT 是公用的,多个程序都可以在里面定义自己的段描述符。
全局描述符表位于内存中,需要用专门的寄存器 GDTR 指向它后,CPU 才知道它在哪里。
GDTR 是个 48 位的寄存器,用来存储 GDT 的内存地址及大小。
lgdt 指令位 GDTR 寄存器进行初始化。
lgdt 指令格式是:lgdt48 <48位内存数据>。48 位内存数据分为两部分,前 16 位是 GDT 以字节为单位的界限值,所以这 16 位相当于 GDT 的字节大小减 1。后 32 位是 GDT 的起始地址。GDT 最多可容纳 8192 个段或门。
在保护模式下,段寄存器 CS、DS、ES、FS、GS、SS 存储的是 选择子。选择子 基本上是个索引值。
选择子是 16 位的。其低 2 位存储 RPL,即请求特权级,可以表示 0、1、2、3 四种特权级。第 2 位是 TI 位,TI 为 0 表示在 GDT 中索引描述符,TI 为 1 表示在 LDT 中索引描述符。
GDT 的第 0 个段描述符是不可用的。
A20Gate:
如果 A20Gate 被打开,当访问到 0x100000 ~ 0x10FFEF 之间的地址时,CPU 将真正访问这块物理内存如果 A20Gate 被禁止,当访问到 0x100000 ~ 0x10FFEF 之间的地址时,CPU 将采用 8086/8088 的地址回绕将端口 0x92 的第 1 位置置 1 就可以打开 A20Gate:
in al, 0x92 or al, 0000_0010B out 0x92, alCR0 寄存器的第 0 位,即 PE 位,用于启用保护模式。
PE 为 0 表示实模式,为 1 表示保护模式。
进入保护模式:
mov eax, cr0 or eax, 0x00000001 mov cr0, eax两个坑点:
boot.inc 中 DESC_LIMIT_VIDEO2 定义有误。进入保护模式后,显存段的基地址是 0xb8000,因此 DESC_LIMIT_VIDEO2 的最低字节应该是 8 (段基址的 23 ~ 16 位):
DESC_LIMIT_VIDEO2 equ 00000000_00000000_00000000_00001011b
编译后 loader.bin 的大小是 618 字节,已经超过一个扇区的大小,将 loader.bin 写入硬盘时应指定 count=2:
dd if=boot/loader.bin of=hd60M.img bs=512 count=2 seek=2 conv=notrunc
代码:github仓库,tag: chapter4。
描述符表基地址 + 选择子中的索引值 * 8 + 7 <= 描述符表基地址 + 描述符表界限值
EIP 中的偏移地址 + 指令长度 - 1 <= 实际段界限大小
偏移地址 + 数据长度 - 1 <= 实际段界限大小
