逻辑地址-=>线性地址-=>物理地址
前面我们提到了当使用80x86微处理器时,有三种不同的地址:
逻辑地址(logical address):包含在机器语言指令中用来指定一个操作数或一条指令的地址。这个寻址方式在80x86著名的分段结构中表现得尤为具体,它促使MS-DOS或Windows程序员把程序分成若干段。每一个逻辑地址都由一个段(segment)和偏移量(offset或displacement)组成,偏移量指明了从段开始的地方到实际地址之间的距离。
线性地址(linear address)(也称虚拟地址virtual address):是一个32位无符号整数,可以用来表示高达4GB的地址,也就是,高达4294967296个内存单元。线性地址通常用十六进制数字表示,范围:0x0000 0000到0xffff ffff。
物理地址(physical address):用于内存芯片级内存单元寻址。它们与从微处理器的地址引脚发送到内存总线上的电信号相对应。物理地址由32位或36位无符号整数表示。
内存控制单元(MMU)通过一种称为分段单元(segmentation unit)的硬件电路把一个逻辑地址转换成线性地址;接着,第二个称为分页单元(paging unit)的硬件电路把线性地址转换成一个物理地址。
分段管理机制
段寄存器
8086的段寄存器
为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址:CS(Code Segment):代码段寄存器;DS(Data Segment):数据段寄存器;SS(Stack Segment):堆栈段寄存器;ES(Extra Segment):附加段寄存器。
当一个程序要执行时,就要决定程序代码、数据和堆栈各要用到内存的哪些位置,通过设定段寄存器 CS,DS,SS 来指向这些起始位置。通常是将DS固定,而根据需要修改CS。所以,程序可以在可寻址空间小于64K的情况下被写成任意大小。 所以,程序和其数据组合起来的大小,限制在DS 所指的64K内,这就是COM文件不得大于64K的原因。
寄存器名 | 描述 |
---|---|
代码段寄存器CS | 存放当前执行的程序的段地址 |
数据段寄存器DS | 存存放当前执行的程序所用操作数的段地址 |
堆栈段寄存器SS | 存放当前执行的程序所用堆栈的段地址 |
附加段寄存器ES | 存放当前执行程序中一个辅助数据段的段地址 |
段寄存器是因为对内存的分段管理而设置的。
计算机需要对内存分段,以分配给不同的程序使用(类似于硬盘分页)。在描述内存分段时,需要有如下段的信息:1.段的大小;2.段的起始地址;3.段的管理属性(禁止写入/禁止执行/系统专用等)。
保护模式下的段寄存器
而在80386以后的CPU中段值需要用8个字节(64位)存储这些信息,但段寄存器只有16位,因此在实模式下,段寄存器含有段值,为访问存储器形成物理地址时,处理器引用相应的某个段寄存器并将其值乘以16,形成20位的段基地址。在保护模式下,段寄存器含有段选择子
如上所述,为了访问存储器形成线性地址时,处理器要使用选择子所指定的描述符中的基地址等信息。为了避免在每次存储器访问时,都要访问描述符表而获得对应的段描述符,从80286开始每个段寄存器都配有一个高速缓冲寄存器,称之为段描述符高速缓冲寄存器或描述符投影寄存器,对程序员而言它是不可见的。每当把一个选择子装入到某个段寄存器时,处理器自动从描述符表中取出相应的描述符,把描述符中的信息保存到对应的高速缓冲寄存器中。此后对该段访问时,处理器都使用对应高速缓冲寄存器中的描述符信息,而不用再从描述符表中取描述符在保护模式下段寄存器中只能存储段号(segment selector,也译作“段选择符”),再由段号映射到存在内存中的GDT(global (segment) descriptor table,全局段号记录表),读取段的信息。
一个逻辑地址由两部分组成:一个段标识符和一个指定段内相对地址的偏移量。段标识符是一个16位长的字段,称为段选择符(Segment Selector),而偏移量是一个32位长的字段。
保护模式下,处理器提供段寄存器,段寄存器的唯一目的是存放段选择符。这些段寄存器称为cs、ss、ds、es、fs和gs。尽管只有6个段寄存器,但程序可以把同一个段寄存器用于不同的目的,方法是先将其值保存在内存中,用完后再恢复。
下面三个段寄存器有专门的用途:
cs:代码段寄存器,指向包含程序指令的段。
ss:栈段寄存器,指向包含当前程序栈的段。
ds:数据段寄存器,指向包含静态数据或者全局数据段。
其它三个段寄存器作一般用途,可以指向任意的数据段。
cs寄存器还有一个很重要的功能:它包含一个两位的字段,用以指明CPU的当前特权级(CPL)。值为0代表最高优先级,值为3代表最低优先级。Linux只用0级和3级,分别称为内核态和用户态。
前面讲了那么多,我们不禁要问了,实模式下的地址转换就是段基址左移四位+段内偏移,那么保护模式下的地址转换是如何完成的呢。
段定义和虚拟地址到线性地址的转换
段是实现虚拟地址到线性地址转换机制的基础。在保护方式下,每个段由如下三个参数进行定义:段基地址(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个字节。在保护方式下,每一个段都有一个相应的描述符来描述。按描述符所描述的对象来划分,描述符可分为如下三类:存储段描述符、系统段描述符、门描述符(控制描述符)。下面先介绍存储段描述符。
存储段描述符的格式
存储段是存放可由程序直接进行访问的代码和数据的段。存储段描述符描述存储段,所以存储段描述符也被称为代码和数据段描述符。存储段描述符的格式如下表所示。表中上面一排是对描述符8个字节的使用的说明,最低地址字节(假设地址为m)在最右边,其余字节依次向左,直到最高字节(地址为m+7)。
下一排是对属性域各位的说明。
从上表可知
段基址
长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)G为就是段界限粒度(Granularity)位
G=0表示界限粒度为字节;G=1表示界限粒度为4K 字节。注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。
- (2)D/B位是一个很特殊的位在描述可执行段、向下扩展数据段或由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兼容。
- (3)AVL位是软件可利用位
80386对该位的使用未做规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。此为被linux和windows操作系统忽略。
- (4)P位称为存在(Present)位
P=1表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中;P=0表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。
- (5)DPL表示描述符特权级(Descriptor Privilege level)
共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。
- (6)DT位说明描述符的类型
对于存储段描述符而言,
DT=1,以区别与系统段描述符和门描述符(DT=0)。
- (7)TYPE说明存储段描述符所描述的存储段的具体属性
-
存储段描述符中的TYPE字段所说明的属性可归纳为下表:
各个位的含义总结为下表
其中的位0指示描述符是否被访问过(Accessed),用符号A标记。
A=0表示尚未被访问,
A=1 表示段已被访问,
当把描述符的相应选择子装入到段寄存器时,80386把该位置为1,表明描述符已被访问,操作系统可测试访问位,已确定描述符是否被访问过。
此为位1,对应的type为奇数,因此type对应的数值为奇数时标识该描述符被访问过,否则为偶数标识未被访问过(可从前面的表中看出)
其中的位3指示所描述的段是代码段还是数据段,用符号E标记
E=0表示段为数据段,相应的描述符也就是数据段(包括堆栈段)描述符。数据段是不可执行的,但总是可读的。
E=1表示段是可执行段,即代码段,相应的描述符就是代码段描述符。代码段总是不可写的,若需要对代码段进行写入操作,则必须使用别名技术,即用一个可写的数据段描述符来描述该代码段,然后对此数据段进行写入。
此位为1时,type所对应的数值>=8(1000),因此type<8标识为数据段,type>=8标识为代码段(可从前面的表中看出)
在数据段描述符中(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表示对应的代码段是一致代码段。关于一致代码段的说明,后面的文章将会详细介绍。
此外,描述符内第6字节中的位5必须置为0,可以理解成是为以后的处理器保留的。
综上我们可以看到在类型不同时,段描述符的区别之处,如下所示
代码段描述符
表示这个段描述符代表一个代码段,它可以放在GDT或LDT中。该描述符置S标志为1。
数据段描述符
表示这个段描述符代表一个数据段,它可以放在GDT或LDT中,该描述符置S标志为1。
任务状态段描述符
表示这个段描述符代表一个任务状态段,也就是说这个段用于保存处理器寄存器的内容。它只能出现在GDT中,根据相应的进程是否正CPU上运行,其Type字段的值分别为11或9。这个描述符的S标志置为0。
局部描述符表描述符
表示这个段描述符代表一个包含LDT的段,它只出现在GDT中。相应的Type字段的值加2,S标志置为0。
全局描述符表GDT和局部描述符表GDT
一个任务会涉及多个段,每个任务需要一个描述符来描述,为了便于组织管理,80386把描述符组织成线性表。由描述符组成的线性表称为描述符表。在80386中有三种类型的描述符表:全局描述符表GDT(Global Descriptor Table)、局部描述符表LDT(Local Descriptor Table)和中断描述符表IDT(Interrupt Descriptor Table)。
在整个系统中,全局描述符表GDT和中断描述符表IDT只有一张,局部描述符表可以有若干张,每个任务可以有一张。
每个描述符表本身形成一个特殊的数据段。这样的特殊数据段最多可包含有8K(8192)个描述符.
每个任务的局部描述符表LDT含有该任务自己的代码段、数据段和堆栈段的描述符,也包含该任务所使用的一些门描述符,如任务门和调用门描述符等。随着任务的切换,系统当前的局部描述符表LDT也随之切换。
全局描述符表GDT含有每一个任务都可能或可以访问的段的描述符,通常包含描述操作系统所使用的代码段、数据段和堆栈段的描述符,也包含多种特殊数据段描述符,如各个用于描述任务LDT的特殊数据段等。
在任务切换时,切换LDT,并不切换GDT
通过LDT可以使各个任务私有的各个段与其它任务相隔离,从而达到受保护的目的。通过GDT可以使各任务都需要使用的段能够被共享。
一个任务可使用的整个虚拟地址空间分为相等的两半,一半空间的描述符在全局描述符表中,另一半空间的描述符在局部描述符表中。由于全局和局部描述符表都可以包含多达8192个描述符,而每个描述符所描述的段的最大值可达4G字节,因此最大的虚拟地址空间可为:
4GB*8192*2=64MMB=64TB
段选择子
在实模式下,逻辑地址空间中存储单元的地址由段值和段内偏移两部分组成。在保护方式下,虚拟地址空间(相当于逻辑地址空间)中存储单元的地址由段选择子和段内偏移两部分组成。与实模式相比,段选择子代替了段值。
段选择子长16位,其格式如下表所示。从表中可见,段选择子的高13位是描述符索引(Index)。所谓描述符索引是指描述符在描述符表中的序号。段选择子的第2位是引用描述符表指示位,标记为TI(Table Indicator),TI=0指示从全局描述符表GDT中读取描述符;TI=1指示从局部描述符表LDT中读取描述符。
段选择子
在实模式下,逻辑地址空间中存储单元的地址由段值和段内偏移两部分组成。在保护方式下,虚拟地址空间(相当于逻辑地址空间)中存储单元的地址由段选择子和段内偏移两部分组成。与实模式相比,段选择子代替了段值。
段选择子长16位,其格式如下表所示。
从表中可见,段选择子的高13位是描述符索引(Index)。所谓描述符索引是指描述符在描述符表中的序号。段选择子的第2位是引用描述符表指示位,标记为TI(Table Indicator),TI=0指示从全局描述符表GDT中读取描述符;TI=1指示从局部描述符表LDT中读取描述符。
选择子确定描述符,描述符确定段基地址,段基地址与偏移之和就是线性地址。所以,虚拟地址空间中的由选择子和偏移两部分构成的二维虚拟地址,就是这样确定了线性地址空间中的一维线性地址。
选择子的最低两位是请求特权级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控制寄存器和系统地址寄存器如下表所示。它们用于控制工作方式,控制分段管理机制及分页管理机制的实施。
控制寄存器
从上表可见,80386有四个32位的控制寄存器,分别命名位CR0、CR1、CR2和CR3。但CR1被保留,供今后开发的处理器使用,在80386中不能使用CR1,否则会引起无效指令操作异常。
CR0包括指示处理器工作方式的控制位,包含启用和禁止分页管理机制的控制位,包含控制浮点协处理器操作的控制位。
CR2及CR3由分页管理机制使用。CR0中的位5—位30及CR3中的位0至位11是保留位,这些位不能是随意值,必须为0。
控制寄存器CR0的低16位等同于80286的机器状态字MSW。
保护控制位
控制寄存器CR0中的位0用PE标记,位31用PG标记,这两个位控制分段和分页管理机制的操作,所以把它们称为保护控制位。
PE控制分段管理机制。
PE=0,处理器运行于实模式;
PE=1,处理器运行于保护方式。
PG控制分页管理机制。
PG=0,禁用分页管理机制,此时分段管理机制产生的线性地址直接作为物理地址使用;
PG=1,启用分页管理机制,此时线性地址经分页管理机制转换位物理地址。
关于分页管理机制的具体介绍在后面的文章中进行。
下表列出了通过使用PE和PG位选择的处理器工作方式。
由于只有在保护方式下才可启用分页机制,所以尽管两个位分别为0和1共可以有四种组合,但只有三种组合方式有效。PE=0且PG=1是无效组合,因此,用PG为1且PE为0的值装入CR0寄存器将引起通用保护异常。
需要注意的是,PG位的改变将使系统启用或禁用分页机制,因而只有当所执行的程序的代码和至少有一部分数据在线性地址空间和物理地址空间具有相同的地址的情况下,才能改变PG位。
协处理器控制位
控制寄存器CR0中的位1—位4分别标记为MP(算术存在位)、EM(模拟位)、TS(任务切换位) 和ET(扩展类型位),它们控制浮点协处理器的操作。
当处理器复位时,ET位被初始化,以指示系统中数字协处理器的类型。
如果系统中存在 80387协处理器,那么ET位置1;如果系统中存在80287协处理器或者不存在协处理器,那么ET位清0。
EM位控制浮点指令的执行是用软件模拟,还是由硬件执行。
EM=0时,硬件控制浮点指令传送到协处理器;
EM=1时,浮点指令由软件模拟。
TS位用于加快任务的切换,通过在必要时才进行协处理器切换的方法实现这一目的。每当进行任务切换时,处理器把TS置1。
TS=1时,浮点指令将产生设备不可用(DNA)异常。
MP位控制WAIT指令在TS=1时,是否产生DNA异常。
MP=1和TS=1时,WAIT产生异常;
MP=0时,WAIT指令忽略TS条件,不产生异常。
CR2和CR3
控制寄存器CR2和CR3由分页管理机制使用。
CR2用于发生页异常时报告出错信息。当发生页异常时,处理器把引起页异常的线性地址保存在CR2中。
操作系统中的页异常处理程序可以检查CR2的内容,从而查出线性地址空间中的哪一页引起本次异常。
CR3用于保存页目录表的其始物理地址。由于目录是页对齐的,所以仅高20位有效,低12 位保留未用。
向CR3中装入一个新值时,低12位必须为0;但从CR3中取值时,低12位被忽略。
每当用MOV指令重置CR3的值时,会导致分页机制高速缓冲区的内容无效,用此方法,可以在启用分页机制之前,即把PG位置1之前,预先刷新分页机制的高速缓存。
CR3寄存器即使在CR0寄存器的PG位或PE位为0时也可装入,如在实模式下也可设置CR3,以便进行分页机制的初始化。
在任务切换时,CR3要被改变,但是如果新任务中CR3的值与原任务中CR3的值相同,那么处理器不刷新分页高速缓存,以便当任务共享也表时有较快的执行速度。
系统地址寄存器
全局描述符表GDT、局部描述符表LDT和中断描述符表IDT等都是保护方式下非常重要的特殊段,它们包含有为段机制所用的重要表格。
为了方便快速地定位这些段,处理器采用一些特殊的寄存器保存这些段的基地址和段界限。我们把这些特殊的寄存器称为系统地址寄存器。
全局描述符表寄存器GDTR
在整个系统中,全局描述符表GDT只有一张(一个处理器对应一个GDT),GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此积存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。
GDTR长48位,其中高32位为基地址,低16位为界限。由于GDT 不能有GDT本身之内的描述符进行描述定义,所以处理器采用GDTR为GDT这一特殊的系统段提供一个伪描述符。GDTR给定了GDT。
GDTR中的段界限以字节为单位。由于段选择子中只有13位作为描述符索引,而每个描述符长8个字节,所以用16位的界限足够。通常,对于含有N个描述符的描述符表的段界限设为8*N-1。
局部描述符表寄存器LDTR
局部描述符表寄存器LDTR规定当前任务使用的局部描述符表LDT。
LDTR类似于段寄存器,由程序员可见的16位的寄存器和程序员不可见的高速缓冲寄存器组成。实际上,每个任务的局部描述符表LDT作为系统的一个特殊段,由一个描述符描述。而用于描述符LDT的描述符存放在GDT中。
在初始化或任务切换过程中,把描述符对应任务LDT的描述符的选择子装入LDTR,处理器根据装入LDTR可见部分的选择子,从GDT中取出对应的描述符,并把LDT的基地址、界限和属性等信息保存到LDTR的不可见的高速缓冲寄存器中。
随后对LDT的访问,就可根据保存在高速缓冲寄存器中的有关信息进行合法性检查。
LDTR寄存器包含当前任务的LDT的选择子。所以,装入到LDTR的选择子必须确定一个位于GDT中的类型为LDT的系统段描述符,也即选择子中的TI位必须是0,而且描述符中的类型字段所表示的类型必须为LDT。
可以用一个空选择子装入LDTR,这表示当前任务没有LDT。在这种情况下,所有装入到段寄存器的选择子都必须指示GDT中的描述符,也即当前任务涉及的段均由GDT中的描述符来描述。
如果再把一个TI位为1的选择子装入到段寄存器,将引起异常。
中断描述符表寄存器IDTR
中断描述符表寄存器IDTR指向中断描述符表IDT。
IDTR长48 位,其中32位的基地址规定IDT的基地址,16位的界限规定IDT的段界限。
由于80386只支持256个中断/异常,所以IDT表最大长度是2K,以字节位单位的段界限为7FFH。IDTR 指示IDT的方式与GDTR指示GDT的方式相同。
任务状态段寄存器TR
任务状态段寄存器TR包含指示描述当前任务的任务状态段的描述符选择子,从而规定了当前任务的状态段。
TR也有程序员可见和不可见两部分。当把任务状态段的选择子装入到TR可见部分时,处理器自动把选择子所索引的描述符中的段基地址等信息保存到不可见的高速缓冲寄存器中。
在此之后,对当前任务状态段的访问可快速方便地进行。装入到TR的选择子不能为空,必须索引位于GDT中的描述符,且描述符的类型必须是TSS。
虚拟地址转换为线性地址
为了将虚拟地址转换为线性地址,分段单元执行如下操作。
先检查段选择子的T1字段,已决定段描述符号,存储在全局段描述符表GDL还是局部段描述符表中。如果在全局段描述符表GDL中,则分段单元则从GDTR中得到GDT的线性基地址,相反如果局部段描述符表中则从LDTR中获取到LDT的线性基地址。
从段选择子的index索引字段,计算段描述符的地址,index字段的值乘以8(一个段描述符的大小是8个字节),然后这个值(偏移)与GDTR或者LDTR的寄存器的值(全局/局部段描述符表的基地址)相加,全局/局部段描述符表中偏移为index*8的这个地址就存储着我们当前段的段描述符。
把得到的段描述符的base字段与逻辑地址额偏移offset值相加就得到了线性地址
至此我们逻辑地址到线性地址的转换就完成了。