从宏观上来说,你的电脑(其实是你的cpu)将会经历以下三个阶段:
实模式
保护模式
IA-32e模式(长模式)
其中的IA-32e模式也就是我们最终日常使用时电脑所处于的状态。
不同模式之间的区别是什么?
模式之间的区别主要是寻址方式之间的差别。也就是在不同模式状态下,cpu与内存之间的互动规则不同。并且实模式下最大寻址范围为220Byte=1MB,保护模式下最大寻址范围为232Byte=4GB,IA-32e模式下最大寻址范围为2^64Byte=17179869184GB(理论上)
为什么要经历这三个模式?不能直接就是IA-32e模式吗?
这么做的目的是为了软件兼容性考虑,因为最初的8086cpu只有实模式这一个状态,大家编写的程序自然就运行在保护模式下。等过了一段时间,intel推出了支持保护模式的80286cpu,假如新的cpu直接舍弃了实模式而直接让cpu运行在保护模式下,那么以前大家所写的只能在实模式下运行的软件就无法在这块新的cpu上运行了。所以intel在后续新cpu推出的时候都不会舍弃以前cpu的运行模式,而是设置了几个开关,由用户(操作系统)自行控制cpu的模式。
0.计算机运行的基本原理
0.1 CS:IP
CS:IP?这其实是两个东西。CS(code segment)代码段寄存器,IP(instruction pointer)指令指针寄存器。这两个东西是cpu里的两个结构,cpu通过这两个寄存器,经过一定规则(这个规则在不同的模式下是不同的)的变化转换成地址,然后就运行内存相应地址上的代码。
0.2 内存结构
你可以把内存想象成一连串带有编号的盒子。cpu根据上述的cs:ip找到对应的编号,然后取出盒子里面的代码来执行。
1.实模式
1.1 实模式的寻址方式
通过前面所描述的内容可以得知,不同模式之间主要的区别就在于cpu寻址方式之间的差别。在实模式下,寻址的规则为:
段寄存器
指针寄存器
实际内存地址
上述计算都是16进制下的计算。其中10h为16进制下的10,等价于10进制下的16。
数字前方0x代表着此数为16进制数,或者在数字后方添加h也同样代表16进制。
也就是说,当cs=0x1111, ip=0x1111
时,cpu将会运行0x1111*10h+0x1111
也就是0x12221
处的代码。
1.2 最初的时刻
在接通电源那一刻,cpu就处于实模式中。
以下出自《intel开发人员手册卷3A-part1》
也就是说,在开机后最初的时刻,cpu会将运行处于地址0xfffffff0
里的代码。也就是我们通常所说的BIOS启动。经过一系列指令后,BIOS启动完成,BIOS将控制权交给用户。
1.3 真正开始接管对电脑的控制
在上述的BIOS启动中,所有的代码都是事先写好在硬件上的,用户并不参与控制。直到BIOS执行结束,用户才开始真正地控制电脑。
在BIOS交出控制权时,此时的cs=0x0000,ip=0x7c00
,也就是说,cpu下一句执行的程序位于0x7c00
这个地址,并且BIOS会将检查硬盘的第一个扇区中的最后两个字节,如果最后两个字节的数据为0xaa55
,则会将第一个扇区内共512字节的数据放置到0x7c00-0x7e00
中。在实模式下,最大的寻址空间只有1MB,而如今的内存地址早已普遍在8GB左右,所以电脑并不会在实模式下停留太久时间,而是立刻开始进入保护模式。
2.保护模式
2.1 保护模式下的寻址方式
在保护模式下,寻址方式不同于实模式的段寄存器:指针寄存器
,取而代之的是段选择子:偏移地址
的寻址方式。此时的cs:ip
所寻找的地址就不再是cs
10h
ip
。而且仅由cs:ip
寄存器在保护模式下是无法寻找到地址的,我们还需要给lgdt
寄存器赋予gdt
表的基地址以及限长。寻址方式如下图:
假设此时cs=0008,ip=00000000
,gdt
基地址为0x7e00
,那么此时的段选择子为cs=0x0008=1000B
,则段选择子会在gdt
基地址0x7e00
上寻找第一个段描述符,此描述符的地址为0x7e08
,其中段描述符的结构为:
取得段描述符中的段基址后再与偏移地址相加,就可以获得物理地址。
2.2 进入保护模式
从BIOS接管了电脑的控制权后,便会开始着手进入保护模式。
《intel开发人员手册3A-part1》
可知,需要进入保护模式,需要准备好以下数据结构:IDT(Interrupt Descriptor Table)中断描述符表
,GDT(Global Descriptor Table)全局描述符表
,TSS(task state segment)任务状态段
因为只是暂时使用保护模式,一进入保护模式便开始准备进入IA-32e模式,所以不对IDT
,TSS
进行设置。进入保护模式的示意代码如下:
lgdt [gdt_size]
;====== open A20 address
in al, 0x92
or al, 00000010B
out 0x92, al
;====== open protect mode
mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x0008:0x00000000
gdt_start_desc dq 0x0000000000000000
gdt_code_desc dq 0x00cf9a000000ffff
gdt_data_desc dq 0x00cf92000000ffff
gdt_size dw $ - gdt_start_desc
gdt_base dd gdt_start_desc
此时cpu进入保护模式,并且随着一句长转跳jmp 0x0008:0x00000000
,cpu开始运行第一个段描述符中的偏移为0处地址的程序,此时运行代码的地址为0x0
3.IA-32e模式
3.1 IA-32e的寻址方式
IA-32e模式的寻址方式,在保留了段选择子的情况下,简化了段描述符的基址与限长,使得段描述符可以直接寻址所有内存空间。并且在开启IA-32e模式的过程中,cpu强制开启页表结构,由对内存的分页来实现对内存的管理。
在IA-32e模式中,寻址不仅需要段选择子:偏移地址
与GDT
,还需要页表
,通过段选择子:偏移地址
与GDT
形成的地址称为Canonical型地址
,经过4重页表转换后就成为了物理地址。页表
与全局描述符表
一样,也是由用户自行设置,基地址由用户操作放入cr3
寄存器。
3.2 进入IA-32e模式
《intel开发人员手册3A-part1》
可知:
IA-32e模式需要从保护模式中启动,并且需要关闭分页功能
开启物理地址扩展PAE
将第四层页表基地址加载至CR3寄存器
通过将IA32_EFER.LME=1开启IA-32e模式
开启分页功能。
示意代码如下:
mov dword [0x5000], 0x6007
mov dword [0x6000], 0x7007
mov dword [0x7000], 0x8007
mov dword [0x8000], 0x000
mov dword [0x8038], 0x7007
lgdt [gdt64_size]
mov eax, cr4
bts eax, 5
mov cr4, eax
mov eax, 0x5000
mov cr3, eax
mov ecx, 0c0000080h
rdmsr
bts eax, 8
wrmsr
mov eax, cr0
bts eax, 31
bts eax, 0
mov cr0, eax
start_desc dq 0x0000000000000000
code_desc dq 0x0000980000000000
data_desc dq 0x0000920000000000
gdt64_size dw $-start_desc
gdt64_base dd start_desc
至此,cpu就进入我们日常使用的IA-32e模式了,接下来就是由操作系统来接管电脑的控制。