本章的题目是“保护模式”,主要目的是能够让CPU进入32位保护模式。前面提到,CPU在启动时是运行在实时模式下的,在实时模式下采用的是16位操作数和16位寻址模式,可用的最大内存为1M。这在今天看来是远远不够的,所以后来CPU被加入32位保护模式(关于16位的保护模式我们不关心,以后所提到的保护模式均为32位保护模式)。 在保护模式下,CPU对程序有着更好的内存保护和32位寻址模式。CPU的32位寻址模式表示它可以使用32根内存地址总线,也就是说它可以使用内存大小为:
下面我们就来一起学习一下在保护模式下如何使用内存。前面章节提到过在实时模式下一个有效的内存地址采用段地址:偏移地址方式来表示。使用CPU使用段寄存器来存放段地址,比如:ds、es、ss、cs等。那么在保护模式下这些段寄存器则不再存放段地址了,而是存放一个叫做“选择子”的数值。其实“选择子”这个词并不准确,但我们不去细分析这个名词解释的问题,只是来看它的具体的作用。CPU在保护模式下是这样来寻址的:它将可用内存分为多个区域,每个区域有一个基地址,而这个区域里面的每一个地址都是相对于这个基地址的逻辑地址。
比如:前面我们提到过显存缓冲区的内存地址区域为0x000b8000 – 0x000bffff,我们可以定义这个区域的基地址为0x000b8000,于是这个区域的里的地址都可以相对于这个其地址来表示:
基地址:0x000b8000
物理地址
相对地址
0x000b8000
0x00000000
0x000b8001
0x00000001
0x000b8002
0x00000002
0x000b8003
0x00000003
0x000b8004
0x00000004
… …
… …
又如:我们还有一个地址为0x00838684,我们可以定义这个区域的基地址为0x00838684,于是这个地址之后的地址都可以相对于这个其地址来表示:
基地址:0x00838684
物理地址
相对地址
0x00838684
0x00000000
0x00838685
0x00000001
0x00838686
0x00000002
0x00838687
0x00000003
0x00838688
0x00000004
… …
… …
在内存中我们需要有地方来存放这些区域的基地址,这个地方被称作GDT(全局描述字符表),这张表中存放了多个区域的GDT,每一个GDT中都会存放上面我们提到的基地址。来看一下GDT具体的内容是什么样子的:
我们从这个GDT的0到64位来分别看一下它们所代表的意思:
Limit(0-15):这个地址区域线性长度的0-15位。
Base Address(0-15):基地址的0-15 位。
Base Address(16-23):基地址的16-23 位。
A:存取信息,上一次存取是读(=0)还是写(=1)。
Type
位41:对数据/堆栈段而言,可写(=1);对代码段而言可读(=1)。
位42:对数据/堆栈段而言表示扩展方向,向下(=1);对代码段而言confirming(=1)。
位43:是代码段(=1)还是数据/堆栈段(=0)。
位44:对代码/数据段必须是1。
DPL:描述符权限级,使用0级内核权限和3级用户权限。
P:段是否存在。
Limit(16-19):这个地址区域线性长度的16-19位。
U:用户定义。
X:未使用。
D:指令和数据是32位(=1)还是16位(=0)。
G:长度单位是4KB还是1字节。
Base Address(24-31)基地址的23-31 位。
理论上讲GDT可以被放在内存的任何位置,那么当通过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,所以CPU的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,我们将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器。CPU就根据此积存器中的内容作为GDT的入口来访问GDT了。
我们要使用一个地址还是采用es:di或ds:si这种方式来寻址,与实时模式的区别是段寄存器中存放的是GDT列表中的索引,而地址寄存器中存放的则是这个GDT里相对于基地址的偏移地址。比如我们的GDT中有3个地址区域,分别基地址分别为0x00000000、0x000b8000和0x00838684。我们就可以通过GDT的选择子来访问这3个地址区域了。先看一下选择子的格式:
对于T/I和RPL是属于CPU保护模式权限的功能,我们先不去管它,内核程序的T/I和RPL我们就设定为0即可。Index是1、2、3、4等(0为保留值)。那么对于选择子来说,它的值就是0x8、0x10、0x18、0x20等。于是,想要访问这3个地址区域,就可以使用下面的方法:
访问物理地址0x00000000:es为0x8、edi为0x0。
访问物理地址0x00000040:es为0x8、edi为0x40。
访问物理地址0x000b8000:es为0x10、edi为0x0。
访问物理地址0x000b8084:es为0x10、edi为0x84。
访问物理地址0x00838684:es为0x18、edi为0x0。
访问物理地址0x008386ad:es为0x18、edi为0x29。