二.分段管理机制
本文介绍保护方式下的段定义以及由段选择子及段内偏移构成的二维虚拟地址如何被转换为一维线性地址。
<一>段定义和虚拟地址到线性地址的转换
段是实现虚拟地址到线性地址转换机制的基础。在保护方式下,每个段由如下三个参数进行定义:段基地址(Base Address)、段界限(Limit)和段属性(Attributes)。
段基地址规定线性地址空间中段的开始地址。在80386保护方式下,段基地址长32位。因为基地址长度与寻址地址的长度相同,所以任何一个段都可以从32位线性地址空间中的任何一个字节开始,而不象实方式下规定的边界必须被16整除。
段界限规定段的大小。在80386保护模式下,段界限用20位表示,而且段界限可以是以字节为单位或以4K字节为单位。段属性中有一位对此进行定义,把该位成为粒度位,用符号G标记。G=0表示段界限以字节位位单位,于是20位的界限可表示的范围是1字节至1M字节,增量为1字节;G=1表示段界限以4K字节为单位,于是20位的界限可表示的范围是4K字节至4G字节,增量为4K字节。当段界限以4K字节为单位时,实际的段界限LIMIT可通过下面的公式从20 位段界限Limit计算出来:
LIMIT=limit*4K+0FFFH=(Limit SHL 12)+0FFFH
所以当粒度为1时,段的界限实际上就扩展成32位。由此可见,在80386保护模式下,段的长度可大大超过64K字节。
基地址和界限定义了段所映射的线性地址的范围。基地址Base是线性地址对应于段内偏移为 0的虚拟地址,段内偏移为X的虚拟地址对应Base+X的线性地址。段内从偏移0到Limit范围内的虚拟地址对应于从Base到Base+Limit范围内的线性地址。
下图表示一个段如何从虚拟地址空间定位到线性地址空间。图中BaseA等代表段基地址, LimitA等代表段界限。另外,段C接在段A之后,也即BaseC=BaseA+LimitA。
例如:设段A的基地址等于00012345H,段界限等于5678H,并且段界限以字节为单位(G=0),那么段A对应线性地址空间中从00012345H-000179BDH的区域。如果段界限以4K字节为单位 (G=1),那么段A对应线性地址空间中从00012345H-0568B344H(=00012345H+5678000H+0FFFH) 的区域。
通过增加段界限,可以使段的容量得到扩展。这对于那些要在内存中扩展容量的普通数据段很有效,但对堆栈段情况就不是这样。因为堆栈底在高地址端,随着压栈操作的进行,堆栈向低地址方向扩展。为了适应普通数据段和堆栈数据段在两个相反方向上的扩展,数据段的段属性中安排了一个扩展方向位,标记为ED。ED=0表示向高端扩展,ED=1表示向低端扩展。一般只有堆栈数据段才使用向低端扩展的属性(堆栈段也可使用向上扩展的段),这是因为,向下扩展的段是为以下两个目的而设计的:
第一,堆栈段被定义为独特段,即DS和SS包含不同的选择器。
第二,一个堆栈段是靠将它复制到一个更大的段来扩充自己(而不是靠将现存的页增加到它的段上)。不打算用这种方法实现堆栈的设计者不需要定义向下扩展的段。
需要注意的是,只有数据段的段属性中才有扩展方向属性位ED,也就是说只有数据段(堆栈段作为特殊的数据段)才有向上扩展和向下扩展之分,其它段都是自然的向上扩展。
数据段的扩展方向和段界限一起决定了数据段内偏移的有效范围。当段最大为1M字节时,在向高端扩展的段内,从0到Limit的偏移是合法有效的偏移,而从Limit+1到1M-1的偏移是非法无效的偏移;在向低端扩展的段内,情形刚好相反,从0到Limit的偏移是非法无效的偏移,而从Limit+1到1M-1的偏移是合法有效的偏移,注意边界值Limit对应地址的有效性。段最大为4G时,情形类似。由此可见,如果一个段是向下扩展的,则所有的偏移必须大于限长,因为其限长是指下限,其基地址从高地址出开始。反之,若一个段是向上扩展的,则所有偏移必须小于等于限长,因为其限长是指上限,基地址从低地址处开始。通过使用段环绕,可以把向下扩展段定义到任何线性地址且可定义为任何大小。
在每次把虚拟地址转换为线性地址的过程中,要对偏移进行检查。如果偏移不在有效的范围内,那么就引起异常。
段属性规定段的主要特性。例如上面已经提到的段粒度G就是段属性的一部分。在对段进行各种访问时,将对访问是否合法进行检查,主要依据是段属性。例如:如果向一个只读段进行写入操作,那么不仅不能写入,而且会引起异常。在下面会详细说明各个段熟属性位的定义和作用。
<二>存储段描述符
用于表示上述定义段的三个参数的数据结构称为描述符。每个描述符长8个字节。在保护方式下,每一个段都有一个相应的描述符来描述。按描述符所描述的对象来划分,描述符可分为如下三类:存储段描述符、系统段描述符、门描述符(控制描述符)。下面先介绍存储段描述符。
1.存储段描述符的格式
存储段是存放可由程序直接进行访问的代码和数据的段。存储段描述符描述存储段,所以存储段描述符也被称为代码和数据段描述符。存储段描述符的格式如下表所示。表中上面一排是对描述符8个字节的使用的说明,最低地址字节(假设地址为m)在最右边,其余字节依次向左,直到最高字节(地址为m+7)。下一排是对属性域各位的说明。
存储段
描述符
m+7
m+6
m+5
m+4
m+3
m+2
m+1
m+0
Base(31...24)
Attributes
Segment Base(23...0)
Segment Limite(15...0)
存储段
描述符
属 性
Byte m+6
Byte m+5
BIT7
BIT6
BIT5
BIT4
BIT3
BIT2
BIT1
BIT0
BIT7
BIT6
BIT5
BIT4
BIT3
BIT2
BIT1
BIT0
G
D
0
AVL
Limit(19...16)
P
DPL
DT1
TYPE
从上表可知,长32位的段基地址(段开始地址)被安排在描述符的两个域中,其位0—位23 安排在描述符内的第2—第4字节中,其位24—位31被安排在描述符内的第7字节中。长20 位的段界限也被安排在描述符的两个域中,其位0—位15被安排在描述符内的第0—第1字节中,其位16—位19被安排在描述符内的第6字节的低4位中。
使用两个域存放段基地址和段界限的原因与80286有关。在80286保护方式下,段基地址只有24位长,而段界限只有16位长。80286存储段描述符尽管也是8字节长,但实际只使用低 6字节,高2字节必须置为0。80386存储段描述符这样的安排,可使得80286的存储段描述符的格式在80386下继续有效。
80386描述符中的段属性也被安排在两个域中。下面对其定义及意义作说明。
(1)P位称为存在(Present)位。P=1表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中;P=0表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。
(2)DPL表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。
(3)DT位说明描述符的类型。对于存储段描述符而言,DT=1,以区别与系统段描述符和门描述符(DT=0)。
(4)TYPE说明存储段描述符所描述的存储段的具体属性。
其中的位0指示描述符是否被访问过(Accessed),用符号A标记。A=0表示尚未被访问,A=1 表示段已被访问。当把描述符的相应选择子装入到段寄存器时,80386把该位置为1,表明描述符已被访问。操作系统可测试访问位,已确定描述符是否被访问过。
其中的位3指示所描述的段是代码段还是数据段,用符号E标记。E=0表示段为数据段,相应的描述符也就是数据段(包括堆栈段)描述符。数据段是不可执行的,但总是可读的。 E=1表示段是可执行段,即代码段,相应的描述符就是代码段描述符。代码段总是不可写的,若需要对代码段进行写入操作,则必须使用别名技术,即用一个可写的数据段描述符来描述该代码段,然后对此数据段进行写入。
在数据段描述符中(E=0的情况),TYPE中的位1指示所描述的数据段是否可写,用W标记。 W=0表示对应的数据段不可写。反之,W=1表示数据段是可写的。注意,数据段总是可读的。TYPE中的位2是ED位,指示所描述的数据段的扩展方向。ED=0表示数据段向高端扩展,也即段内偏移必须小于等于段界限。ED=1表示数据段向低扩展,段内偏移必须大于段界限。
在代码段描述符中(E=1的情况),TYPE中的位1指示所描述的代码段是否可读,用符号R标记。R=0表示对应的代码段不可读,只能执行。R=1表示对应的代码段可读可执行。注意代码段总是不可写的,若需要对代码段进行写入操作,则必须使用别名技术。在代码段中,TYPE中的位2指示所描述的代码段是否是一致代码段,用C标记。C=0表示对应的代码段不是一致代码段(普通代码段),C=1表示对应的代码段是一致代码段。关于一致代码段的说明,后面的文章将会详细介绍。
存储段描述符中的TYPE字段所说明的属性可归纳为下表:
数据段
类 型
类型值
说 明
0
只读
1
只读、已访问
2
读/写
3
读/写、已访问
4
只读、向下扩展
5
只读、向下扩展、已访问
6
读/写、向下扩展
7
读/写、向下扩展、已访问
代码段
类 型
类型值
说 明
8
只执行
9
只执行、已访问
A
执行/读
B
执行/读、已访问
C
只执行、一致码段
D
只执行、一致码段、已访问
E
执行/读、一致码段
F
执行/读、一致码段、已访问
(5)G为就是段界限粒度(Granularity)位。G=0表示界限粒度为字节;G=1表示界限粒度为4K 字节。注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。
(6)D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。
在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段;D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。
在向下扩展数据段的描述符中,D位决定段的上部边界。D=1表示段的上部界限为4G;D=0表示段的上部界限为64K,这是为了与80286兼容。
在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。D=1表示使用32位堆栈指针寄存器ESP;D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。
(7)AVL位是软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。
此外,描述符内第6字节中的位5必须置为0,可以理解成是为以后的处理器保留的。
2.存储段描述符的结构类型表示
根据存储段描述符的结构,可定义如下的汇编语言描述符结构类型:
DESC STRUC
LIMITL DW 0 ;段界限低16位
BASEL DW 0 ;基地址低16位
BASEM DB 0 ;基地址中间8位
ATTRIB DB 0 ;段属性
LIMITH DB 0 ;段界限的高4位(包括段属性的高4位)
BASEH DB 0 ;基地址的高8位
DESC ENDS
利用结构类型DESC能方便地在程序中说明存储段描述符。例如:下面的描述符DATAS描述一个可读写的有效(存在的)数据段,基地址是100000H,以字节为单位的界限是0FFFFH,描述符特权级DPL=3。
DATAS DESC <0FFFFH,,10H,0F2H,,>
再如:下述描述符CODEA描述一个只可执行的有效的32位代码段,基地址是12345678H,以 4K字节位单位的段界限值是10H(以字节位单位的界限是10FFFH),描述符特权级DPL=0。
CODEA DESC <10H,5678H,34H,98H,0C0H,12H>
<三>全局和局部描述符表
一个任务会涉及多个段,每个任务需要一个描述符来描述,为了便于组织管理,80386把描述符组织成线性表。由描述符组成的线性表称为描述符表。在80386中有三种类型的描述符表:全局描述符表GDT(Global Descriptor Table)、局部描述符表LDT(Local Descriptor Table)和中断描述符表IDT(Interrupt Descriptor Table)。在整个系统中,全局描述符表GDT和中断描述符表IDT只有一张,局部描述符表可以有若干张,每个任务可以有一张。
例如,下列描述符表有6个描述符构成:
DESCTAB LABEL BYTE
DESC1 DESC <1234H,5678H,34H,92H,,>
DESC1 DESC <1234H,5678H,34H,93H,,>
DESC1 DESC <5678H,1234H,56H,98H,,>
DESC1 DESC <5678H,1234H,56H,99H,,>
DESC1 DESC <0FFFFH,,10H,16H,,>
DESC1 DESC <0FFFFH,,10H,90H,,>
每个描述符表本身形成一个特殊的数据段。这样的特殊数据段最多可包含有8K(8192)个描述符.
关于中断描述符表IDT在以后的文章中介绍。
每个任务的局部描述符表LDT含有该任务自己的代码段、数据段和堆栈段的描述符,也包含该任务所使用的一些门描述符,如任务门和调用门描述符等。随着任务的切换,系统当前的局部描述符表LDT也随之切换。
全局描述符表GDT含有每一个任务都可能或可以访问的段的描述符,通常包含描述操作系统所使用的代码段、数据段和堆栈段的描述符,也包含多种特殊数据段描述符,如各个用于描述任务LDT的特殊数据段等。在任务切换时,并不切换GDT。
通过LDT可以使各个任务私有的各个段与其它任务相隔离,从而达到受保护的目的。通过GDT可以使各任务都需要使用的段能够被共享。下图给出了任务A和任务B所涉及的有关段既隔离受保护,又合用共享的情况。通过任务A的局部描述符表LDTA和任务B的局部描述符表LDTB,把任务A所私有的代码段CodeA及数据段DataA与任务B所私有的代码段CodeB和数据段DataB及DataB2隔离,但任务A和任务B通过全局描述符表GDT共享代码段CodeK及CodeOS和数据段DataK及DataOS。
一个任务可使用的整个虚拟地址空间分为相等的两半,一半空间的描述符在全局描述符表中,另一半空间的描述符在局部描述符表中。由于全局和局部描述符表都可以包含多达8192个描述符,而每个描述符所描述的段的最大值可达4G字节,因此最大的虚拟地址空间可为:
4GB*8192*2=64MMB=64TB
<四>段选择子
在实模式下,逻辑地址空间中存储单元的地址由段值和段内偏移两部分组成。在保护方式下,虚拟地址空间(相当于逻辑地址空间)中存储单元的地址由段选择子和段内偏移两部分组成。与实模式相比,段选择子代替了段值。
段选择子长16位,其格式如下表所示。从表中可见,段选择子的高13位是描述符索引(Index)。所谓描述符索引是指描述符在描述符表中的序号。段选择子的第2位是引用描述符表指示位,标记为TI(Table Indicator),TI=0指示从全局描述符表GDT中读取描述符;TI=1指示从局部描述符表LDT中读取描述符。
选择子
结 构
BIT15
BIT14
BIT13
BIT12
BIT11
BIT10
BIT9
BIT8
BIT7
BIT6
BIT5
BIT4
BIT3
BIT2
BIT1
BIT0
描述符索引
TI
RPL
选择子确定描述符,描述符确定段基地址,段基地址与偏移之和就是线性地址。所以,虚拟地址空间中的由选择子和偏移两部分构成的二维虚拟地址,就是这样确定了线性地址空间中的一维线性地址。
选择子的最低两位是请求特权级RPL(Requested Privilege Level),用于特权检查。 RPL字段的用法如下:
每当程序试图访问一个段时,要把当前特权级与所访问段的特权级进行比较,以确定是否允许程序对该段的访问。使用选择子的RPL字段,将改变特权级的测试规则。在这种情况下,与所访问段的特权级比较的特权级不是CPL,而是CPU与RPL中更外层的特权级。 CPL存放在CS寄存器的RPL字段内,每当一个代码段选择子装入CS寄存器中时,处理器自动地把CPL存放到CS的RPL字段。
由于选择子中的描述符索引字段用13位表示,所以可区分8192个描述符。这也就是描述符表最多包含8192个描述符的原因。由于每个描述符长8字节,根据上表所示选择子的格式,屏蔽选择子低3位后所得的值就是选择子所指定的描述符在描述符表中的偏移,这可认为是安排选择子高13位作为描述符索引的原因。
有一个特殊的选择子称为空(Null)选择子,它的Index=0,TI=0,而RPL字段可以为任意值。空选择子有特定的用途,当用空选择子进行存储访问时会引起异常。空选择子是特别定义的,它不对应于全局描述符表GDT中的第0个描述符,因此处理器中的第0个描述符总不被处理器访问,一般把它置成全0。但当TI=1时,Index为0的选择子不是空选择子,它指定了当前任务局部描述符表LDT中的第0个描述符。
<五>段描述符高速缓冲寄存器
在实模式下,段寄存器含有段值,为访问存储器形成物理地址时,处理器引用相应的某个段寄存器并将其值乘以16,形成20位的段基地址。在保护模式下,段寄存器含有段选择子,如上所述,为了访问存储器形成线性地址时,处理器要使用选择子所指定的描述符中的基地址等信息。为了避免在每次存储器访问时,都要访问描述符表而获得对应的段描述符,从80286开始每个段寄存器都配有一个高速缓冲寄存器,称之为段描述符高速缓冲寄存器或描述符投影寄存器,对程序员而言它是不可见的。每当把一个选择子装入到某个段寄存器时,处理器自动从描述符表中取出相应的描述符,把描述符中的信息保存到对应的高速缓冲寄存器中。此后对该段访问时,处理器都使用对应高速缓冲寄存器中的描述符信息,而不用再从描述符表中取描述符。
各段描述符高速缓冲寄存器之内容如下表所示。其中,32位段基地址直接取自描述符, 32位的段界限取自描述符中20位的段界限,并根据描述符属性中的粒度位转换成以字节为单位。其它十个特性根据描述符中的属性而定,“Y”表示“是”,“N”表示“否” ,“R”表示必须可读,“W”表示必须可写,“P”表示必须存在,“D”表示根据描述符中属性而定。
段描
述符
高速
缓冲
寄存
器的
内容
段寄存器
段基地址
段界限
段属性
存在性
特权级
已存取
粒度
扩展方向
可读性
可写性
可执行
堆栈大小
一致特权
CS
32位基地址
32位段界限
P
D
D
D
D
D
N
Y
-
D
SS
32位基地址
32位段界限
P
D
D
D
D
R
W
N
D
-
DS
32位基地址
32位段界限
P
D
D
D
D
D
D
N
-
-
ES
32位基地址
32位段界限
P
D
D
D
D
D
D
N
-
-
FS
32位基地址
32位段界限
P
D
D
D
D
D
D
N
-
-
GS
32位基地址
32位段界限
P
D
D
D
D
D
D
N
-
-
段描述符高速缓冲寄存器再处理器内,所以可对其进行快速访问。绝大多数情况下,对存储器的访问是在对应选择子装入到段寄存器之后进行的,所以,使用段描述符高速缓冲寄存器可以得到很好的执行性能。
段描述符高速缓冲寄存器之内保存的描述符信息将一直保存到重新把选择子装载到段寄存器时再更新。程序员尽管不可见段描述符高速缓冲寄存器,但必须注意到它的存在和它的上述更新时机。例如,在改变了描述符表中的某个当前段的描述符后,也要更新对应的段描述符高速缓冲寄存器的内容,即使段选择子未作改变,这可通过重新装载段寄存器实现。
参考资料
书 名
出 版 社
作 者
《保护方式下的80386及其编程》
清华大学出版社
周明德主编
《80X86汇编语言程序设计教程》
清华大学出版社
扬季文主编