前面我们已经说明了使用段选择符来定位描述符表中的一个描述符。段描述符是GDT和LDT表中的一个数据结构项,用于向处理器提供有关一个段的位置和大小信息以及访问控制的状态信息。每个段描述符的长度是8字节,含有3个主要字段:段基地址、段限长和段属性。段描述符通常由编译器、链接器、加载器或者操作系统来创建,但绝不是应用程序。图4-13给出了所有类型段描述符的一般格式。
(点击查看大图)图4-13 段描述符通用格式 |
一个段描述符中各字段和标志的含义如下:
(1)段限长字段Limit(Segment limit field):用于指定段的长度。处理器会把段描述符中两个段限长字段组合成一个20位的值,并根据颗粒度标志G来指定段限长Limit值的实际含义。如果G=0,则段长度Limit范围可从1B~1MB,单位是1B;如果G=1,则段长度Limit范围可从4KB~4GB,单位是4KB。
根据段类型中的段扩展方向标志E,处理器以两种不同方式使用段限长Limit。对于向上扩展的段(简称上扩段),逻辑地址中的偏移值范围可以从0到段限长值Limit。大于段限长Limit的偏移值将产生一般保护性异常。对于向下扩展的段(简称下扩段),段限长Limit的含义相反。根据默认栈指针大小标志B的设置,偏移值范围可从段限长Limit到0xFFFFFFFF或0xFFFF。而小于段限长Limit的偏移值将产生一般保护性异常。对于下扩段,减小段限长字段中的值会在该段地址空间底部分配新的内存,而不是在顶部分配。80x86的栈总是向下扩展的,因此这种实现方式很适合扩展堆栈。
(2)基地址字段Base(Base address field):该字段定义在4GB线性地址空间中一个段字节0所处的位置。处理器会把3个分立的基地址字段组合形成一个32位的值。段基地址应该对齐16字节边界。虽然这不是要求的,但通过把程序的代码和数据段对齐在16字节边界上,可以让程序具有最佳性能。
(3)段类型字段TYPE(Type field):用行指定段或门(Gate)的类型、说明段的访问种类以及段的扩展方向。该字段的解释依赖于描述符类型标志S指明是一个应用(代码或数据)描述符还是一个系统描述符。TYPE字段的编码对代码、数据或系统描述符都不同,如图4-14所示。
(点击查看大图)图4-14 代码段、数据段和系统段描述符格式 |
(4)描述符类型标志S(Descriptor type flag):用于指明一个段描述符是系统段描述符(当S=0)还是代码或数据段描述符(当S=1)。
(5)描述符特权级字段DPL(Descriptor privilege level):用于指明描述符的特权级。特权级范围从0到3。0级特权级最高,3级最低。DPL用于控制对段的访问。
(6)段存在标志P(Segment present):用于指出一个段是在内存中(P=1)还是不在内存中(P=0)。当一个段描述符的P标志为0时,那么把指向这个段描述符的选择符加载进段寄存器将导致产生一个段不存在异常。内存管理软件可以使用这个标志来控制在某一给定时间实际需要把那个段加载进内存中。这个功能为虚拟存储提供了除分页机制以外的控制。图4-15给出了当P=0时的段描述符格式。当P标志为0时,操作系统可以自由使用格式中标注为可用(Available)的字段位置来保存自己的数据,例如有关不存在段实际在什么地方的信息。
(点击查看大图)图4-15 当存在位P=0时的段描述符格式 |
(7)D/B(默认操作大小/默认栈指针大小和/或上界限)标志(Default operation size/default stack pointer size and/or upper bound):根据段描述符描述的是一个可执行代码段、下扩数据段还是一个堆栈段,这个标志具有不同的功能。(对于32位代码和数据段,这个标志应该总是设置为1;对于16位代码和数据段,这个标志被设置为0。)
可执行代码段。此时这个标志称为D标志并用于指出该段中的指令引用有效地址和操作数的默认长度。如果该标志置位,则默认值是32位地址和32位或8位的操作数;如果该标志为0,则默认值是16位地址和16位或8位的操作数。指令前缀0x66可以用来选择非默认值的操作数大小;前缀0x67可用来选择非默认值的地址大小。
栈段(由SS寄存器指向的数据段)。此时该标志称为B(Big)标志,用于指明隐含堆栈操作(如PUSH、POP或CALL)时的栈指针大小。如果该标志置位,则使用32位栈指针并存放在ESP寄存器中;如果该标志为0,则使用16位栈指针并存放在SP寄存器中。如果堆栈段被设置成一个下扩数据段,这个B标志也同时指定了堆栈段的上界限。
下扩数据段。此时该标志称为B标志,用于指明堆栈段的上界限。如果设置了该标志,则堆栈段的上界限是0xFFFFFFFF(4GB);如果没有设置该标志,则堆栈段的上界限是0xFFFF(64KB)。
(8)颗粒度标志G(Granularity):该字段用于确定段限长字段Limit值的单位。如果颗粒度标志为0,则段限长值的单位是字节;如果设置了颗粒度标志,则段限长值使用4KB单位。(这个标志不影响段基地址的颗粒度,基地址的颗粒度总是字节单位。)若设置了G标志,那么当使用段限长来检查偏移值时,并不会去检查偏移值的12位最低有效位。例如,当G=1时,段限长为0表明有效偏移值为0~4095。
(9)可用和保留位(Available and reserved bits):段描述符第2个双字的位20可供系统软件使用;位21是保留位并应该总是设置为0。
代码和数据段描述符类型
当段描述符中S(描述符类型)标志被置位,则该描述符用于代码或数据段。此时类型字段中最高位(第2个双字的位11)用于确定是数据段的描述符(复位)还是代码段的描述符(置位)。
对于数据段的描述符,类型字段的低3位(位8、9、10)被分别用于表示已访问A(Accessed)、可写W(Write-enable)和扩展方向E(Expansion-direction),参见表4-3中有关代码和数据段类型字段位的说明。根据可写位W的设置,一个数据段可以是只读的,也可以是可读可写的。
表4-3 代码段和数据段描述符类型
类型(TYPE)字段 |
描述符类型 |
说明 |
||||
十进制 |
位11 |
位10 |
位9 |
位8 |
||
|
|
E |
W |
A |
|
|
0 |
0 |
0 |
0 |
0 |
数据 |
只读 |
1 |
0 |
0 |
0 |
1 |
数据 |
只读,已访问 |
2 |
0 |
0 |
1 |
0 |
数据 |
可读/写 |
3 |
0 |
0 |
1 |
1 |
数据 |
可读/写,已访问 |
4 |
0 |
1 |
0 |
0 |
数据 |
向下扩展,只读 |
5 |
0 |
1 |
0 |
1 |
数据 |
向下扩展,只读,已访问 |
6 |
0 |
1 |
1 |
0 |
数据 |
向下扩展,可读/写 |
7 |
0 |
1 |
1 |
1 |
数据 |
向下扩展,可读/写,已访问 |
|
|
C |
R |
A |
|
|
8 |
1 |
0 |
0 |
0 |
代码 |
仅执行 |
9 |
1 |
0 |
0 |
1 |
代码 |
仅执行,已访问 |
10 |
1 |
0 |
1 |
0 |
代码 |
执行/可读 |
11 |
1 |
0 |
1 |
1 |
代码 |
执行/可读,已访问 |
12 |
1 |
1 |
0 |
0 |
代码 |
一致性段,仅执行 |
13 |
1 |
1 |
0 |
1 |
代码 |
一致性段,仅执行,已访问 |
14 |
1 |
1 |
1 |
0 |
代码 |
一致性段,执行/可读 |
15 |
1 |
1 |
1 |
1 |
代码 |
一致性段,执行/可读,已访问 |
堆栈段必须是可读/写的数据段。若使用不可写数据段的选择符加载到SS寄存器中,将导致一个一般保护异常。如果堆栈段的长度需要动态地改变,那么堆栈段可以是一个向下扩展的数据段(扩展方向标志置位)。这里,动态改变段限长将导致栈空间被添加到栈底部。
已访问位指明自从上次操作系统复位该位之后一个段是否被访问过。每当处理器把一个段的段选择符加载进段寄存器,它就会设置该位。该位需要明确地清除,否则一直保持置位状态。该位可用于虚拟内存管理和调试。
对于代码段,类型字段的低3位被解释成已访问A(Accessed)、可读R(Read-enable)和一致的C(Conforming)。根据可读R标志的设置,代码段可以是只能执行、可执行/可读。当常数或其他静态数据以及指令码被放在了一个ROM中时就可以使用一个可执行/可读代码段。这里,通过使用带CS前缀的指令或者把代码段选择符加载进一个数据段寄存器(DS、ES、FS或GS),我们可以读取代码段中的数据。在保护模式下,代码段是不可写的。
代码段可以是一致性的或非一致性的。向更高特权级一致性代码段的执行控制转移,允许程序以当前特权级继续执行。向一个不同特权级的非一致性代码段的转移将导致一般保护异常,除非使用了一个调用门或任务门(有关一致性和非一致性代码段的详细信息请参见"直接调用或跳转到代码段")。不访问保护设施的系统工具以及某些异常类型(例如除出错、溢出)的处理过程可以存放在一致性代码段中。需要防止低特权级程序或过程访问的工具应该存放在非一致性代码段中。
所有数据段都是非一致性的,即意味着它们不能被低特权级的程序或过程访问。然而,与代码段不同,数据段可以被更高特权级的程序或过程访问,而无须使用特殊的访问门。
如果GDT或LDT中一个段描述符被存放在ROM中,那么若软件或处理器试图更新(写)在ROM中的段描述符时,处理器就会进入一个无限循环。为了防止这个问题,需要存放在ROM中的所有描述符的已访问位应该预先设置成置位状态。同时,删除操作系统中任何试图修改ROM中段描述符的代码。
系统描述符类型
当段描述符中的S标志(描述符类型)是复位状态(0)的话,那么该描述符是一个系统描述符。处理器能够识别以下一些类型的系统段描述符:
局部描述符表(LDT)的段描述符。
任务状态段(TSS)描述符。
调用门描述符。
中断门描述符。
陷阱门描述符。
任务门描述符。
这些描述符类型可分为两大类:系统段描述符和门描述符。系统段描述符指向系统段(如LDT和TSS段),门描述符就是一个"门",对于调用、中断或陷阱门,其中含有代码段的选择符和段中程序入口点的指针;对于任务门,其中含有TSS的段选择符。表4-4给出了系统段描述符和门描述符类型字段的编码。
表4-4 系统段和门描述符类型
类型(TYPE)字段 |
说明 |
|||||
十进制 |
位11 |
位10 |
位9 |
位8 |
||
0 |
0 |
0 |
0 |
0 |
Reserved |
保留 |
1 |
0 |
0 |
0 |
1 |
16-Bit TSS (Available) |
16位 TSS(可用) |
2 |
0 |
0 |
1 |
0 |
LDT |
LDT |
3 |
0 |
0 |
1 |
1 |
16-Bit TSS (Busy) |
16位 TSS(忙) |
4 |
0 |
1 |
0 |
0 |
16-Bit Call Gate |
16位调用门 |
5 |
0 |
1 |
0 |
1 |
Task Gate |
任务门 |
6 |
0 |
1 |
1 |
0 |
16-Bit Interrupt Gate |
16位中断门 |
7 |
0 |
1 |
1 |
1 |
16-Bit Trap Gate |
16位陷阱门 |
8 |
1 |
0 |
0 |
0 |
Reserved |
保留 |
9 |
1 |
0 |
0 |
1 |
32-Bit TSS (Available) |
32位TSS(可用) |
10 |
1 |
0 |
1 |
0 |
Reserved |
保留 |
11 |
1 |
0 |
1 |
1 |
32-Bit TSS (Busy) |
32位TSS(忙) |
12 |
1 |
1 |
0 |
0 |
32-Bit Call gate |
32位调用门 |
13 |
1 |
1 |
0 |
1 |
Reserved |
保留 |
14 |
1 |
1 |
1 |
0 |
32-Bit Interrupt Gate |
32位中断门 |
15 |
1 |
1 |
1 |
1 |
32-Bit Trap Gate |
32位陷阱门 |