1.实模式下的寻址
用户初次接管cpu的控制权时,cpu正处于实模式下。此时cpu通过cs:ip,即代码段寄存器:指令指针寄存器来寻址,具体的寻址算法为物理地址=cs10h+ip。
2.保护模式的作用
由于在实模式下寻址方式为cs+ip,其中cs代码段寄存器长度为16位,ip指令指针寄存器长度也为16位,则寻址范围在0x00000~0xfffff之间,也就是说最大只能使用到1M内存。而操作系统的大小现如今早已超过了1M,所以我们需要进入保护模式从而让可读取的内存范围增加到4GB,这样放置操作系统就没有问题了。
3.保护模式下的寻址
在保护模式下,寻址依然由cs:ip也就是代码段寄存器与指令指针寄存器决定,而此时的寻址方式则与实模下有很大的不同。此时由cs代码段寄存器(其中内容称为段选择子)在GDT(全局描述符)中选择段描述符,而由ip指令指针寄存器决定偏移量,其中ip指令指针为32位,则寻址范围最多可以达到4GB
3.1保护模式寻址方式
3.2全局描述符表
作为存放段描述符的容器,需要手动将其建立并通过lgdt指令加载到内存之中。其中描述符表最开始的16字节全为0。以下通过代码示范全局描述符表数据结构:
;=== 头64位全为0
gdt_start_desc dq 0x0000000000000000
;=== 代码段描述符,参考3.4段描述符定义
gdt_code_desc dq 0x00cf9a000000ffff
;=== 数据段描述符,参考3.4段描述符定义
gdt_data_desc dq 0x00cf92000000ffff
;=== 描述符表限长
gdt_size dw $ - gdt_start_desc
;=== 描述符基址
gdt_base dd gdt_start_desc
然后通过lgdt [gdt_size]就可以将GDT基址指针对准我们刚刚创建的全局描述符表在内存中开始的地址了。
3.3段选择子
在保护模式中,cs代码段寄存器长度依然为16位,其中的内容称为段选择子,其数据结构为:
3.4段描述符
4.从实模式到保护模式
当我们的全局描述符表准备好后,我们便可以开始着手跳转保护模式了。
;=== 设定GDT基址指针,见3.2
lgdt [gdt_size]
;=== 通过0x92端口,开启A20地址门(历史遗留问题)
in al, 0x92
or al, 00000010B
out 0x92, al
;=== 通过将cr0寄存器第0位置1,开启保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
此时就已经进入了保护模式,此时执行jmp 0x0008:0x0,则其中的0x0008为段选择子,0x0为偏移地址。段选择子的二进制表示此时为1000B,则表示段选择子中TL=0,选中的是GDT表,RPL=0表示请求特权级为0,index=1表示选中全局描述符表中的第一个段描述符,也就是我们之前设定的gdt_code_desc代码段描述符。
5.从保护模式到实模式
5.1 16位保护模式
要从保护模式返回到实模式,并不能通过简单地将cr0寄存器最后一位置0返回实模式。因为此时cpu中的cs代码段寄存器还处于32位模式之下,若此时直接从保护模式返回实模式,cs代码段寄存器依然会运行在32位模式之下,从而使得cpu运行在32位代码的实模式下,这将使得原本可以运行在16位实模式下的代码无法运行。所以在从保护模式切换回到实模式之前,我们要让cs代码段寄存器先行返回到16位模式,然后再将cr0寄存器最后一位置0,返回实模式。也就是说先要让cpu进入16位保护模式。
要让cs代码段寄存器进入到16位模式,需要将运行16位代码的段描述符加载至其中。所以,我们在创建GDT表的时候,应该多创建两个段描述符号,用来改变cs代码段寄存器的运行模式。如下:
gdt_start_desc dq 0x0000000000000000
gdt_code_desc dq 0x00cf9a000000ffff
gdt_data_desc dq 0x00cf92000000ffff
;=== 新增加的两个段描述符,与之前的描述符区别在于第54位的D内容不同,详见3.4段描述符
gdt_RM_code_desc dq 0x000f9a000000ffff
gdt_RM_data_desc dq 0x000f92000000ffff
gdt_size dw $ - gdt_start_desc
gdt_base dd gdt_start_desc
5.2 返回实模式
所以,当我们需要从保护模式返回实模式时,我们应该通过
jmp 0x0018:Label_16BitProtectMode
其中0x0018段选择子选择的index=3,也就是将gdt_RM_code_desc这个位于全局描述符表的第三个段描述符加载进了cs代码段寄存器,此时就进入了16位保护模式。现在就可以通过将cr0寄存器第0位置1来返回实模式了。
jmp 0x0018:Label_16BitProtectMode
[BITS 16]
Label_16BitProtectMode:
mov ax, 0x0020
mov ds, ax ; 将数据段寄存器也返回16位
mov eax, cr0
and al , 11111110B
mov cr0, eax
mov ax, 0x00
mov ds, ax ; 重置数据段寄存器
; 此时我们就处于真正的实模式下了。