首先让我们先来熟悉一下概念。一个任务(Task )通常会涉及多个段,每个段需要一个描述符号来描述(当然不是绝对的一对一关系,一个段也可以由多个段描述符来对应,视具体应用而定),为了便于组织管理,80386把描述符组织成线性表。由描述符组成的线性表称为描述符表。在80386中有三种类型的描述符表(Descriptor Table),分别是全局描述符表 GDT(Global Descriptor Table),局部描述符表(Local Descriptor Table)和中断描述符表 IDT(Interrupt Descriptor Table)。在整个系统中,全局描述符表GDT和中断描述符表IDT只有一张,局部描述符表可以有若干张,每个任务可以有一张。
在实模式下,逻辑地址是由段基址和段内偏移构成的。保护模式下,规则发生了很大变化,虚拟地址空间(相当于逻辑地址空间)中存储单元的地址由段选择子和段内偏移两部分组成,与实模式相比,段选择子代替了原来的段基址。从本质上来讲,段选择子最终还是要转化成段基址,那么选择子是如何转化为段基址的呢?让我们来看看选择子的结构和用法:
段选择子长16位,其格式如上图所示。从图中可见,段选择子的高13位是描述符的索引值。所谓描述符索引是指描述符在描述符表中的序号。由于描述符总是8个字节的,所以将描述符索引值逻辑左移3位即可得到对应描述符在描述符表中的偏移地址,再加上描述符表起始地址就可以确定描述符的位置,这算是一个小技巧。段段选择子的第2位是引用描述符表指示位,标记为TI(Table Indicator),TI=0表示该选择子指示的是全局描述符表GDT中的描述符,TI=1表示该选择子指示的是局部描述符表LDT中的描述符。第0和第1位称
为 RPL(Request Privilege Level请求特权级),用于特权级控制,在上一个问题中有详细描述。通过段选择子,我们可以从GDT或 LDT中找到需要的段描述符,段描述符中存储着目标段的基址(起始地址),界限(段的范围)以及其他一些控制信息,由此,我们完成了段选择子到段基址的转化。
到这里,我们似乎离开题目太远了,请不要急,我们惟有将基本的概念陈述清楚,才能将问题回答透彻,那么现在开始回答问题。
如前所述,描述符的线性表构成了GDT,LDT,IDT,这些描述符并非都是用来描述数据段或代码段的,有的可能用来指示一个任务,比如任务门描述符,用于任务切换;有的用来指示子程序,比如调用门;还有的用来指示中断处理程序,如中断门、陷阱门等,当然,中断门和陷阱门只存在于IDT中。除此之外,由于CPU把局部描述符表LDT也当作数据段来管理,所以要求每一个LDT都必须有相应的描述符存在于GDT中,暂且称之为LDT描述符。由此可见,GDT、LDT和IDT是由各种不同种类的描述符排列构成的,他们的组成数据各不相同,作用也不同,唯一相同的是他们都是8字节的,都存于描述符表中,都用选择子来定位(中断门和陷阱门用INT 指令后的数字来做描述符索引)。
前面说到GDT和IDT是整个系统一张,而LDT可以每个任务独占一长,用于存储每个任务私有的段的信息,所以当任务发生切换时,LDT也要随之切换,CPU中专门用一个16位的寄存器LDTR来存储当前任务的LDT在GDT中的描述符的选择子,以此来定位当前任务的LDT。同时也存在这么一种情况,那就是一个任务使用的所有段都是系统全局的,它不需要用LDT来存储私有段信息,因此,当系统切换到这种任务时,会将LDTR寄存器赋值成一个空(全局描述符)选择子,选择子的描述符索引值为0,TI指示位为0,RPL可以为任意值,用这种方式表明当前任务没有 LDT。这里的空选择子因为TI为0,所以它实际上指向了GDT的第0项描述符,第0项的作用类似于C语言中NULL的用法,它虽然是一个描述符,但却只起到到了标志的作用,规定GDT的第0项描述符为空描述符,其8个字节全为0,就是这个原因。如果把前面的空描述符选择子的TI位改为1,使之指向LDT 中的0号描述符,这样的选择子就不是空选择子,它指向的LDT中的0号描述符是可以正常使用的,也就是LDT中没有空描述符一说。