终于进入保护模式了,从放假开始纠结了4天,虽然是冰山一角,但还是很兴奋的~呵呵。之前没接触过汇编,很多概念都不清楚,所以学到了很多。
计算机在开机执行完BIOS进行最初的初始化之后,会跳转到0x7c00处执行引导程序,此时CPU运行在实地址模式下,在实模式下,只能寻址1MB的空间,而且操作系统的代码和数据并没有得到保护,应用程序可能会访问并修改他们,这是很不安全的。而保护模式,顾名思义,是为操作系统提供了保护措施的模式,在保护模式下,应用程序不能直接访问系统代码和数据,只能通过系统调用访问,而且每个进程独享自己4GB的地址空间。而保护模式还有一个更直观且很重要的作用就是它提高了寻址能力,应用程序可以访问4GB的空间。
进入保护模式的主要步骤有
1.创建GDT表
2.载入GDT表
3.打开A20地址线
4.设置CR0的PE位
5.跳转到保护模式
1.创建GDT表
GDT是全局描述符表,用来保存每个段的信息,包括段的限界、基地址和一些属性。描述符的结构如下
高地址………………………………………………………………………低地址
; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
; |7654321076543210765432107654321076543210765432107654321076543210| <- 共 8 字节
; |--------========--------========--------========--------========|
; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓
; ┃31..24┃ (见下图) ┃ 段基址(23..0) ┃ 段界限(15..0)┃
; ┃ ┃ ┃ ┃ ┃
; ┃ 基址2┃③│②│ ① ┃基址1b│ 基址1a ┃ 段界限1 ┃
; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫
; ┃ %6 ┃ %5 ┃ %4 ┃ %3 ┃ %2 ┃ %1 ┃
; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛
; │ /_________
; │ /__________________
; │ /________________________________________________
; │ /
; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
; ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃
; ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫
; ┃ G ┃ D ┃ 0 ┃ AVL┃ 段界限 2 (19..16) ┃ P ┃ DPL ┃ S ┃ TYPE ┃
; ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫
; ┃ ③: 属性 2 ┃ ②: 段界限 2 ┃ ①: 属性1 ┃
; ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛
; 高地址 低地址
在创建GDT时,一定要注意以下几点:
a.Intel是采用小端来保存数据的,我在此采用的是Nasm,Nasm中没有类似于GASM中.quad的指令,所以在Nasm中声明8字节数据时,必须要 按照从右到左的顺序声明。例如
gdt:
dw 0,0,0,0
;代码段描述符
dw 0x0010;1-2字节
dw 0x0000;3-4字节 可执行/写
dw 0x9a00;5-6字节
dw 0x00cf;7-8字节
;显示段描述符
dw 0x0010
dw 0x8000;可读/写
dw 0x920B
dw 0x00cf
而且也要注意,拿代码段描述符的第1-2字节为例,[gdt+8]处的数据是0x10而不是0x00,因为是小端存放的。但是在寄存器中的数据仍然是 0x0010,即如果用访问内存的方式(加中括号)访问数据时,一定要注意存放的顺序,而在访问寄存器时则不用考虑。
b.在进入保护模式后,表达式seg:offset中的seg和实模式下seg的含义是完全不同的。实模式下seg的含义即为某个段的基地址,而保护模式下 seg只是一个索引,它指明了某个段在GDT中的索引,且叫作选择子,它也有自己的结构,如下
; 选择子图示:
; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
; ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9 ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃
; ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫
; ┃ 描述符索引 ┃ TI ┃ RPL ┃
; ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛
其中TI=0表明请求的是在GDT中的段,TI=1表明是一个LDT中的段。RPL是请求特权级。可以看到,一个GDT中最多可以有2^13个段。
2.载入GDT表
载入GDT表,可以用lgdt指令,它将一个结构载入gdtr寄存器,该结构指明了GDT表的地址和GDT的界限。GDTR结构共有六个字节,低两字节 指明GDT的界限,高四字节指明GDT的地址。此处也要注意从右到左的声明顺序。
;GDTR
gdtptr:
dw 0x0010
dw gdt,0;不是0x7c00+gdt
其余三个步骤都较为简单且都差不多,所以不多说了。还要注意的是跳转到保护模式后,所有的代码都应该是在32位模式下,所以要在程序中用[BITS 32]指明。整个程序的代码如下:
程序中代码段和显示段是这样联系的:在disppmmsg中将显示段赋给gs,然后从12行开始,把要执行代码的地址赋给代码段的基地址。此处要执行的代码地址为当前段基址cs左移四位加上偏移。