数据总线
当说一个CPU“16位”,“32位”的时候,指的是处理器中的算术逻辑单元ALU的宽度。
系统总线中的数据线部分,称为数据总线,通常与ALU具有相同宽度。
对于地址总线,最自然的当然是与数据总线宽度一样,一个地址(即指针),最好与一个整数的长度一致。但是在以前,地址总线往往比数据总线大,那时的数据总线宽度还比较小,并且人们对于内存的追求总是越大越好。
段式内存由来
为了解决地址总线与数据总线的不均匀问题,如16位的CPU却涉及到20位数据的操作,人们发明了段式内存。Intel在8086 CPU(16位CPU,20位地址总线)中设置了4个段寄存器: CS, DS, SS, ES。每个段寄存器都是16位,对应地址总线的高16位。每条“访存”指令中的“内部地址”都是16位的,但是在送上地址总线之前都在CPU内部自动地与某个段寄存器中的内容相加,形成一个20位的实际地址。
下面是该映射模式下的一个例子。通过这种映射模式,CPU能以16位的运算,访问到20位的地址空间。
某条访存指令 AC(位于DS段), 访问实际内存地址MD
MD[20-4] = AC[15-4] + DS[16-0]
MD[3-0] = AC[3-0]
保护模式
但是段式内存是不安全的,因为它缺乏对内存空间的保护,为了区分后来的保护模式,因此它又被称为实地址模式。
后来的80386是一个32位CPU,并且地址总线也达到32位,但是它还是不能做到简洁、自然。因为它要兼容前面的系列机,也就是它必须兼容实地址模式,同时又加入了保护模式。
而这也让Intel选择了在段寄存器的基础上构筑保护模式,并且添加了FS, GS 2个段寄存器。
为了实现保护模式,必须加入更多的信息,如访问权限,此时单纯的基地址已经不能满足需求,需要的是一个数据结构,于是基地址变成了指向这样一个结构的指针。
段描述符
每个程序都有自己的LDT,但是同一台计算机上的所有程序共享一个GDT。
LDT描述局部于每个程序的段,包括其代码、数据、堆栈等。GDT描述系统段,包括操作系统本身。
GDT
GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此积存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。GDTR中存放的是GDT在内存中的基地址和其表长界限。
段选择子(Selector)
由GDTR访问全局描述符表是通过“段选择子”(实模式下的段寄存器)来完成的。为了访问一个段,程序必须把这个段的选择子装入机器的6个段寄存器的某一个中。在运行过程中,CS寄存器保存代码段的选择子,DS寄存器保存数据段的选择子。每个选择子是一个16位数。
LDT
由于LDT本身同样是一段内存,也是一个段,所以它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。LDTR可以在程序中随时改变,通过使用lldt指令。如下图,如果装载的是Selector 2则LDTR指向的是表LDT2。
有了局部描述符表则可以将每个进程的程序段、数据段、堆栈段封装在一起,只要改变LDTR就可以实现对不同进程的段进行访问。
下面是保护模式的机制:
- 根据指令性质确定使用哪一个段寄存器,与实地址模式一样。
- 根据段寄存器的内容(索引,表项编号),找到相应的“地址段描述符结构”。
- 从地址段描述符中得到基地址
- 将指令中发出的地址作为位移,与段描述符中规定的长度相比,看看是否越界
- 根据指令的性质和段描述符的访问权限来确定是否越权
- 将指令中发出的地址作为位移,与基地址相加得到实际的“物理地址”
当描述项的基地址为0,段长度设置为最大,此时形成一个从0开始覆盖整个32位地址空间的整段,逻辑地址与物理地址相同,这种特殊的状态叫flat模式。