x86汇编语言——进入32位保护模式
IA-32架构
Intel 32 位处理器架构简称 IA-32(Intel Architecture, 32-bit), 是以 1978 年的 8086 处理器为基础发展起来的。
在 16 位处理器内,有 8 个通用寄存器 AX、 BX、 CX、 DX、 SI、 DI、 BP 和 SP,其中,前 4 个还可以拆分成两个独立的 8 位寄存器来用,即 AH、 AL、 BH、 BL、 CH、 CL、 DH 和DL。

为了在汇编语言程序中使用经过扩展(Extend)的寄存器,需要给它们命名,它们的名字
分别是 EAX、 EBX、 ECX、 EDX、 ESI、 EDI、 ESP 和 EBP。
在 32 位模式下,为了生成 32 位物理地址,处理器需要使用 32 位的指令指针寄存器。为此, 32 位处理器扩展了 IP,使之达到 32 位,即 EIP。当它工作在 16 位模式下时,依然使用 16 位的 IP;工作在 32 位模式下时,使用的是全部的 32 位 EIP。

在 16 位处理器中,标志寄存器 FLAGS 是 16 位的,在 32 位处理器中,扩展到了 32 位,
低 16 位和原先保持一致。
在 32 位模式下,对内存的访问从理论上来说不再需要分段,因为它有 32 根地址线,可以自由访问任何一个内存位置。但是, IA-32 架构的处理器是基于分段模型的,因此, 32 位处理器依然需要以段为单位访问内存,即使它工作在 32 位模式下。
一种变通的方案,即,只分一个段,段的基地址是 0x00000000,段的长度(大小)是 4GB。在这种情况下, 可以视为不分段,即平坦模型(Flat Mode)。
在 32 位模式下,处理器要求在加载程序时,先定义该程序所拥有的段,然后允
许使用这些段。定义段时,除了基地址(起始地址)外,还附加了段界限、特权级别、类型等属性。当程序访问一个段时,处理器将用固件实施各种检查工作,以防止对内存的违规访问
在 32 位模式下,传统的段寄存器,如 CS、 SS、 DS、 ES,保存的不再是 16
位段基地址,而是段的选择子,即用于选择所要访问的段,叫做段选择器。除了段选择器之外,每个段寄存器还包括一个 64 位的不可见部分,称为描述符高速缓存器,里面有段的基地址和各种访问属性。这部分内容程序不可访问,由处理器自动使用
80286 第一次提出了保护模式的概念。在保护模式下,段寄存器中保存的不再是段地址,而是段选择子,真正的段地址位于段寄存器的描述符高速缓存中,是 24 位的。80286 处理器访问内存时,不再需要将段地址左移,因为在段寄存器的描述符高速缓存器中有 24 位的段物理基地址。
线性地址
为 IA-32 处理器编程,访问内存时,需要在程序中给出段地址和偏移量,因为分段是 IA-32
架构的基本特征之一。传统上,段地址和偏移地址称为逻辑地址,偏移地址叫做有效地址
(Effective Address, EA),在指令中给出有效地址的方式叫做寻址方式(Addressing Mode)
段的管理是由处理器的段部件负责进行的,段部件将段地址和偏移地址相加,得到访问内存的地址。一般来说,段部件产生的地址就是物理地址。
为解决内存碎片化的问题,IA-32 处理器支持分页功能,分页功能将物理内存空间划分成逻辑上的页。页的大小是固定的,一般为 4KB,通过使用页,可以简化内存管理。当页功能开启时,段部件产生的地址就不再是物理地址了,而是线性地址(Linear Address),线性地址还要经页部件转换后,才是物理地址。
线性地址的概念用来描述任务的地址空间。 IA-32 处理器上的每个任务都拥有4GB 的虚拟内存空间,这是一段长 4GB 的平坦空间,就像一段平直的线段,因此叫线性地址空间。相应地,由段部件产生的地址,就对应着线性地址空间上的每一个点,这就是线性地址。

进入保护模式
在多用户、多任务时代,内存中会有多个用户(应用)程序在同时运行。为了使它们彼此隔离,防止因某个程序的编写错误或者崩溃而影响到操作系统和其他用户程序,使用保护模式是非常有必要的。
GDT 全局描述符表
在进入保护模式前,必须要定义全局描述符表
处理器内部有一个 48 位的寄存器,称为全局描述符表寄存器(GDTR)。该寄存器分为两部分,分别是 32 位的线性地址和 16 位的边界。

GDT 的界限是 16 位的,所以,该表最大是 216 字节,也就是 65536 字节(64KB)。又因
为一个描述符占 8 字节,故最多可以定义 8192 个描述符
在保护模式下,内存的访问机制完全不同,即,必须通过描述符来进行。所以,这些段必须重新在 GDT 中定义。
内存安排如图

存储器的段描述符
每个描述符在 GDT 中占 8 字节,也就是 2 个双字,或者说是 64 位
描述符中的段基地址和段界限不是连续的,这是从 80286 处理器上带来的后遗症。

- 20 位的段界限用来限制段的扩展范围。因为访问内存的方法是用段基地址加上偏移量
- G 位是粒度(Granularity)位,用于解释段界限的含义。当 G 位是“0”时,段界限以字节为单位。如果该位是“1”,那么,段界限是以 4KB 为单位的。
- S 位用于指定描述符的类型(Descriptor Type)。当该位是“0”时,表示是一个系统段;为“1”时,表示是一个代码段或者数据段(堆栈段也是特殊的数据段)。
- DPL 表示描述符的特权级(Descriptor Privilege Level, DPL)。这两位用于指定段的特权级。共有 4 种处理器支持的特权级别,分别是 0、 1、 2、 3,其中 0 是最高特权级别, 3 是最低特权级别。刚进入保护模式时执行的代码具有最高特权级 0(可以看成是从处理器那里继承来的),这些代码通常都是操作系统代码,因此它的特权级别最高。每当操作系统加载一个用户程序时,它通常都会指定一个稍低的特权级,比如 3 特权级。不同特权级别的程序是互相隔离的,其互访是严格限制的
- P 是段存在位(Segment Present)。 P 位用于指示描述符所对应的段是否存在。P 位是由处理器负责检查的。每当通过描述符访问内存中的段时,如果 P 位是“0”,处理器就会产生一个异常中断。
- D/B 位是“默认的操作数大小”(Default Operation Size)或者“默认的堆栈指针大小”(Default Stack Pointer Size),又或者“上部边界”(Upper Bound)标志。
- L 位是 64 位代码段标志(64-bit Code Segment),保留此位给 64 位处理器使用。
- TYPE 字段共 4 位,用于指示描述符的子类型。对于数据段来说, 这 4 位分别是 X、 E、 W、 A 位;而对于代码段来说,这 4 位则分别是 X、 C、 R、 A 位。对于数据段来说, E 位指示段的扩展方向。 E=0 是向上扩展的,也就是向高地址方向扩展的,是普通的数据段; E=1 是向下扩展的,也就是向低地址方向扩展的,通常是堆栈段。 对于代码段来说, C 位指示段是否为特权级依从的(Conforming)。 C=0 表示非依从的代码段,这样的代码段可以从与它特权级相同的代码段调用,或者通过门调用; C=1 表示允许从低特权级的程序转移到该段执行。数据段和代码段的 A 位是已访问(Accessed)位,用于指示它所指向的段最近是否被访问过。
- AVL 是软件可以使用的位(Available),通常由操作系统来用
![在这里插入图片描述]()
用户程序的逻辑,规划好内存之后,将段描述符向段描述符表中填写,然后通过lgdt指令将GDT的界限和位置加载到GDTR中
lgdt m48 ;lgdt m16&m32
该指令的操作数是一个内存地址,指向一个包含了 48 位(6 字节)数据的内存区域。前 16 位是 GDT 的界限值,高 32 位是 GDT 的基地址。在初始状态下(计算机启动之后), GDTR 的基地址被初始化为 0x00000000;界限值为 0xFFFF
A20问题
为了兼容8086中20位地址线的遗留问题(21位地址线强制为0),IBM 公司使用一个与门来控制第 21 根地址线 A20,并把这个与门的控制阀门放在键盘控制器内,端口号是 0x60。不过,这种做法非常烦琐
80486 处理器推出后,处理器本身就有了 A20M#引脚,意思是 A20 屏蔽(A20 Mask),它是低电平有效的。
输入输出控制器集中芯片 ICH 的处理器接口部分,有一个用于兼容老式设备的端口 0x92,第 7~2 位保留未用,第 0 位叫做 INIT_NOW,意思是“现在初始化”,用于初始化处理器,当它从 0 过渡到 1 时, ICH 芯片会使处理器 INIT#引脚的电平变低(有效),并保持至少16 个 PCI 时钟周期。通俗地说,向这个端口写 1,将会使处理器复位,导致计算机重新启动。
端口 0x92 的位 1 用于控制 A20,叫做替代的 A20 门控制(Alternate A20 Gate,ALT_A20_GATE),它和来自键盘控制器的 A20 控制线一起,通过或门连接到处理器的 A20M#引脚。

进入保护模式
CR0 是处理器内部的控制寄存器(Control Register, CR)。之所以有个“0”后缀,是因为还有CR1、 CR2、 CR3 和 CR4 控制寄存器,甚至还有 CR8。
CR0 是 32 位的寄存器,包含了一系列用于控制处理器操作模式和运行状态的标志位。
它的第 1 位(位 0)是保护模式允许位(Protection Enable, PE),如果把该位置“1”,则处理器进入保护模式,按保护模式的规则开始运行。
保护模式下的中断机制和实模式不同,因此,原有的中断向量表不再适用,而且,必须要知道的是,在保护模式下, BIOS 中断都不能再用,在重新设置保护模式下的中断环境之前,必须关中断
保护模式下的内存访问
保护模式下的段寄存器,带有一个不可见的64位称为描述符高速缓存器,用来存放段的线性基地址、段界限和段属性。
在保护模式下访问一个段时,传送到段选择器的是段选择子。它由三部分组成,第一部分是描述符的索引号,用来在描述符表中选择一个段描述符。 TI 是描述符表指示器(Table
Indicator), TI=0 时,表示描述符在 GDT 中; TI=1 时,描述符在 LDT 中。 LDT 的知识将在后面进行介绍,它也是一个描述符表,和 GDT 类似。 RPL 是请求特权级,表示给出当前选择子的那个程序的特权级别。

段选择子的描述符索引X8(每个描述符8字节,64位),加上GDTR中的基地址在GDT中定位到段的线性地址,并将该段描述符加载到高速缓存中

主引导扇区程序
;代码清单12-1
;文件名:c12_mbr.asm
;文件说明:硬盘主引导扇区代码
;创建日期:2011-10-27 22:52
;设置堆栈段和栈指针
mov eax,cs
mov ss,eax
mov sp,0x7c00
;计算GDT所在的逻辑段地址
mov eax,[cs:pgdt+0x7c00+0x02] ;GDT的32位线性基地址
xor edx,edx
mov ebx,16
div ebx ;分解成16位逻辑地址
mov ds,eax ;令DS指向该段以进行操作
mov ebx,edx ;段内起始偏移地址
;创建0#描述符,它是空描述符,这是处理器的要求
mov dword [ebx+0x00],0x00000000
mov dword [ebx+0x04],0x00000000
;创建1#描述符,这是一个数据段,对应0~4GB的线性地址空间
mov dword [ebx+0x08],0x0000ffff ;基地址为0,段界限为0xfffff
mov dword [ebx+0x0c],0x00cf9200 ;粒度为4KB,存储器段描述符
;创建保护模式下初始代码段描述符
mov dword [ebx+0x10],0x7c0001ff ;基地址为0x00007c00,512字节
mov dword [ebx+0x14],0x00409800 ;粒度为1个字节,代码段描述符
;创建以上代码段的别名描述符
mov dword [ebx+0x18],0x7c0001ff ;基地址为0x00007c00,512字节
mov dword [ebx+0x1c],0x00409200 ;粒度为1个字节,数据段描述符
mov dword [ebx+0x20],0x7c00fffe
mov dword [ebx+0x24],0x00cf9600
;初始化描述符表寄存器GDTR
mov word [cs: pgdt+0x7c00],39 ;描述符表的界限
lgdt [cs: pgdt+0x7c00]
in al,0x92 ;南桥芯片内的端口
or al,0000_0010B
out 0x92,al ;打开A20
cli ;中断机制尚未工作
mov eax,cr0
or eax,1
mov cr0,eax ;设置PE位
;以下进入保护模式... ...
jmp dword 0x0010:flush ;16位的描述符选择子:32位偏移
[bits 32]
flush:
mov eax,0x0018
mov ds,eax
mov eax,0x0008 ;加载数据段(0..4GB)选择子
mov es,eax
mov fs,eax
mov gs,eax
mov eax,0x0020 ;0000 0000 0010 0000
mov ss,eax
xor esp,esp ;ESP <- 0
mov dword [es:0x0b8000],0x072e0750 ;字符'P'、'.'及其显示属性
mov dword [es:0x0b8004],0x072e074d ;字符'M'、'.'及其显示属性
mov dword [es:0x0b8008],0x07200720 ;两个空白字符及其显示属性
mov dword [es:0x0b800c],0x076b076f ;字符'o'、'k'及其显示属性
;开始冒泡排序
mov ecx,pgdt-string-1 ;遍历次数=串长度-1
@@1:
push ecx ;32位模式下的loop使用ecx
xor bx,bx ;32位模式下,偏移量可以是16位,也可以
@@2: ;是后面的32位
mov ax,[string+bx]
cmp ah,al ;ah中存放的是源字的高字节
jge @@3
xchg al,ah
mov [string+bx],ax
@@3:
inc bx
loop @@2
pop ecx
loop @@1
mov ecx,pgdt-string
xor ebx,ebx ;偏移地址是32位的情况
@@4: ;32位的偏移具有更大的灵活性
mov ah,0x07
mov al,[string+ebx]
mov [es:0xb80a0+ebx*2],ax ;演示0~4GB寻址。
inc ebx
loop @@4
hlt
;-------------------------------------------------------------------------------
string db 's0ke4or92xap3fv8giuzjcy5l1m7hd6bnqtw.'
;-------------------------------------------------------------------------------
pgdt dw 0
dd 0x00007e00 ;GDT的物理地址
;-------------------------------------------------------------------------------
times 510-($-$$) db 0
db 0x55,0xaa
参考
x86汇编语言:从实模式到保护模式,第11章


浙公网安备 33010602011771号