《基于Linux的C编程与内核导读》连载(14)
时间:2007-06-13 来源:gaowp
4.1.1 Intel X86 CPU系列的寻址方式
在X86系列中,8086和8088是16位处理器,而80386开始为32位处理器,80286则是该系统从8088到80386,也就是16位到32位过渡的一个中间步骤。80286虽然仍是16位处理器,但是在寻址方式上开始了从“实地址模式”到“保护模式”的过渡。
当我们说一个CPU是16位或32位时,指的是处理器中“算术逻辑单元”(ALU)的宽度。系统总线中的数据部分,称为数据总线,通常与ALU具有相同的宽度。而地址总线的宽度却不一定和数据总线一致,比如一个16位的CPU,若地址总线也是16位,那么其所决定的地址空间(64K)还是太小了。当Intel推出其16位CPU8086时,决定采用1M字节的内存地址空间,地址总线的宽度是20位。这样就出现一个问题:虽然地址总线的宽度是20位,然而可以直接在ALU中加以运算的指针长度是16位。结果,Intel设计了一种分段的方法来解决。
Intel在8086CPU中设置了四个“段寄存器”:CS、DS、SS、ES,分别用于可执行代码指令、数据、堆栈和其它。每个段寄存器都是16位的,对应于地址总线中的高16位。每条“访内”指令中的“内部地址”都是16位的,但是在送上地址总线之前都在CPU内部自动地与某个段寄存器中的内容相加,形成一个20位的实际地址。这里要注意段寄存器中的内容对应于20位地址总线中的高16位,所以在相加时实际上是拿内部地址中的高12位与段寄存器中的16位相加,而内部地址中的低4位保留不变。
由此可见,对于一个由段寄存器的内容确定的“基地址”,一个进程总是能够访问从此开始的64K字节的连续地址空间,而无法加以限制。但是,一个CPU如果缺乏对内存访问的限制,或者说保护,就谈不上什么内存管理,也就谈不上是现代意义上的中央处理器。为了区别于后来出现的“保护模式”,8086的这种内存寻址方式称为“实地址模式”。
80386是个32位CPU,为了兼容从前的程序,其必须维持那些段寄存器,还必须支持实地址模式,在此同时又要能支持保护模式。Intel选择了在段寄存器的基础上构筑保护模式的构思,并且保留段寄存器为16位,但是又增加了两个段寄存器FS和GS。为了实现保护模式,光是用段寄存器来确定一个基地址是不够的,至少还得要有一个段地址的长度,和访问权限之类的信息。所以这里需要一个数据结构,而并非一个单纯的基地址。因此在保护模式下要改变段寄存器的功能,使其从一个单纯的基地址变成指向这样一个数据结构的指针。其具体步骤如下:
(1)根据指令的性质来确定应该使用哪一个段寄存器。
(2)根据段寄存器的内容,找到相应的“地址段描述结构”。
(3)从地址段描述结构中得到基地址。
(4)将指令中发出的地址作为位移,与段描述结构中规定的段长度相比,看看是否越界。
(5)根据指令的性质和段描述结构中的访问权限来确定是否越权。
(6)将指令中发出的地址作为位移,与基地址相加得出实际的“物理地址”。
明白了这个思路,80386的段式内存管理机制就比较容易理解了,下面就是此机制的实际实现。
首先,在80386CPU中增设了两个寄存器:一个是全局性段描述表寄存器GDTR,另外一个是局部性段描述表寄存器LDTR,分别可以用来指向存储在内存中的一个段描述结构数组,或者称为段描述表。
在此基础上,段寄存器的高13位用作访问段描述表中具体描述结构的下标,如图4-1所示。
图4-1 段寄存器定义
GDTR或LDTR中的段描述表指针和段寄存器中给出的下标结合在一起,才决定了具体的段描述表项在内存中的什么地方。每个段描述表项的大小是8个字节,其含有段的基地址和段大小等一些信息,其结构如图4-2所示。
图4-2 8字节段描述符表项的定义
每当一个段寄存器的内容改变时,CPU就把由这个段寄存器的新内容所决定的段描述项装入CPU内部的一个“影子”描述项。这样,CPU中有几个寄存器就有几个影子描述项,这一部分对编程者是不可见的。