内存寻址

    内存是指一组有序字节组成的数组,每个字节有唯一的内存地址。内存寻址则是指对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串。80x86支持多种数据类型:1字节、2字节(1个字)或4字节(双字或长字)的无符号整型数或带符号整型数,以及多字节字符串等。通常,字节中某一位的定位或寻址可以基于字节来寻址,因此最小数据类型的寻址是对1字节数据(数值或字符)的定位。通常内存地址从0开始编址,对于80x86 CPU来说,其地址总线宽度为32位,因此一共有232个不同物理地址,即内存物理地址空间有4GB,总共可以寻址4GB的物理内存。对于多字节数据类型(例如2字节整数数据类型),在内存中这些字节相邻存放。80x86首先存放低值字节,随后地址处存放高值字节。因此80x86 CPU是一种先存小值(Little Endian)的处理器。

对于80x86 CPU,一条指令主要由操作码(Opcode)和操作对象即操作数(Oprand)构成。操作数可以位于一个寄存器中,也可以在内存中。若要定位内存中的操作数,就要进行内存寻址。80x86有许多指令的操作数涉及内存寻址,并且针对所寻址对象数据类型的不同,也有很多不同的寻址方案可供选择。

为了进行内存寻址,80x86使用了一种称为段(Segment)的寻址技术。这种寻址技术把内存空间分成一个或多个称为段的线性区域,从而对内存中一个数据对象的寻址就需要使用一个段的起始地址(即段地址)和一个段内偏移地址两部分构成。段地址部分使用16位的段选择符指定,其中14位可以选择214即16384个段。段内偏移地址部分使用32位的值来指定,因此段内地址可以是0~4GB。即一个段的最大长度可达4GB。程序中由16位的段和32位的偏移构成的48位地址或长指针称为一个逻辑地址(虚拟地址)。它唯一确定了一个数据对象的段地址和段内偏移地址。而仅由32位偏移地址或指针指定的地址是基于当前段的对象地址。
80x86为段部分提供了6个存放段选择符的段寄存器:CS、DS、ES、SS、FS和GS。其中CS总是用于寻址代码段,而堆栈段则专门使用SS段寄存器。在任何指定时刻由CS寻址的段称为当前代码段。此时EIP寄存器中包含了当前代码段内下一条要执行指令的段内偏移地址。因此要执行指令的地址可表示成CS:[EIP]。后面将说明的段间控制转移指令可以被用来为CS和EIP赋予新值,从而可以把执行位置改变到其他的代码段中。这样。就实现了在不同段中程序的控制传递。

由段寄存器SS寻址的段称为当前堆栈段。栈顶由ESP寄存器内容指定。因此堆栈顶处地址是SS:[ESP]。另外4个段寄存器是通用段寄存器。当指令中没有指定所操作数据的段时,那么DS将是默认的数据段寄存器。

为了指定内存操作数的段内偏移地址,80x86指令规定了计算偏移量的很多方式,称为指令寻址方式。指令的偏移量由3部分相加组成:基地址寄存器、变址寄存器和一个偏移常量。三者关系如下:

偏移地址=基地址 +(变址×比例因子)+偏移量

 地址变换

任何完整的内存管理系统都包含两个关键部分:保护和地址变换。提供保护措施可以防止一个任务访问另一个任务或操作系统的内存区域。地址变换能够让操作系统在给任务分配内存时具有灵活性,并且因为我们可以让某些物理地址不被任何逻辑地址所映射,所以在地址变换过程中同时也提供了内存保护功能。

正如上面提到的,计算机中的物理内存是字节的线性数组,每字节具有一个唯一的物理地址;程序中的地址是由两部分构成的逻辑地址。这种逻辑地址并不能直接用于访问物理内存,而需要使用地址变换机制将它变换或映射到物理内存地址上。内存管理机制即用于将这种逻辑地址转换成物理内存地址。

为了减少确定地址变换所需要的信息,变换或映射通常以内存块作为操作单位。分段机制和分页机制是两种广泛使用的地址变换技术。它们的不同之处在于逻辑地址如何组织成被映射的内存块、变换信息如何指定以及编程人员如何进行操作。分段和分页操作都使用驻留在内存中的表来指定它们各自的变换信息。这些表只能由操作系统访问,以防止应用程序擅自修改。

80x86在从逻辑地址到物理地址变换过程中使用了分段和分页两种机制,如图4-4所示。第一阶段使用分段机制把程序的逻辑地址变换成处理器可寻址内存空间(称为线性地址空间)中的地址。第二阶段使用分页机制把线性地址转换为物理地址。在地址变换过程中,第一阶段的分段变换机制总是使用的,而第二阶段的分页机制则是供选用的。如果没有启用分页机制,那么分段机制产生的线性地址空间就直接映射到处理器的物理地址空间上。物理地址空间定义为处理器在其地址总线上能够产生的地址范围。

 
(点击查看大图)图4-4  虚拟地址(逻辑地址)到物理地址的变换过程

1.分段机制

分段提供了隔绝各个代码、数据和堆栈区域的机制,因此多个程序(或任务)可以运行在同一个处理器上而不会互相干扰。分页机制为传统需求页、虚拟内存系统提供了实现机制。其中虚拟内存系统用于实现程序代码按要求被映射到物理内存中。分页机制当然也能用于提供多任务之间的隔离措施。

如图4-5所示,分段提供了一种机制,用于把处理器可寻址的线性地址空间划分成一些较小的称为段的受保护地址空间区域。段可以用来存放程序的代码、数据和堆栈,或者用来存放系统数据结构(如TSS或LDT)。如果处理器中有多个程序或任务在运行,那么每个程序可分配各自的一套段。此时处理器就可以加强这些段之间的界限,并且确保一个程序不会通过访问另一个程序的段而干扰程序的执行。分段机制还允许对段进行分类。这样,对特定类型段的操作能够受到限制。

一个系统中所有使用的段都包含在处理器线性地址空间中。为了定位指定段中的一个字节,程序必须提供一个逻辑地址。逻辑地址包括一个段选择符和一个偏移量。段选择符是一个段的唯一标识。另外,段选择符提供了段描述符表(如全局描述符表GDT)中一个数据结构(称为段描述符)的偏移量。每个段都有一个段描述符。段描述符指明段的大小、访问权限和段的特权级、段类型以及段的第1字节在线性地址空间中的位置(称为段的基地址)。逻辑地址的偏移量部分加到段的基地址上就可以定位段中某字节的位置。因此基地址加上偏移量就形成了处理器线性地址空间中的地址。

线性地址空间与物理地址空间具有相同的结构。相对于二维的逻辑地址空间来说,它们都是一维地址空间。虚拟地址(逻辑地址)空间可包含最多16K个段,而每个段最长可达4GB(232B),使得虚拟地址空间容量达到64TB(246)。线性地址空间和物理地址空间都是4GB。实际上,如果禁用分页机制,那么线性地址空间就是物理地址空间。

 
(点击查看大图)图4-5  逻辑地址、线性地址和物理地址之间的变换

2.分页机制

因为多任务系统通常定义的线性地址空间都要比其含有的物理内存容量大得多,所以需要使用某种"虚拟化"线性地址空间的方法,即使用虚拟存储技术。虚拟存储是一种内存管理技术,使用这种技术可让编程人员产生内存空间要比计算机中实际物理内存容量大很多的错觉。利用这种错觉,我们可以随意编制大型程序而无需考虑实际物理内存究竟有多少。

分页机制支持虚拟存储技术。在使用虚拟存储的环境中,大容量的线性地址空间需要使用小块的物理内存(RAM或ROM)以及某些外部存储空间(如大容量硬盘)来模拟。当使用分页时,每个段被划分成页面(通常每页为4KB大小),页面会被存储于物理内存中或硬盘上。操作系统通过维护一个页目录和一些页表来留意这些页面。当程序(或任务)试图访问线性地址空间中的一个地址位置时,处理器就会使用页目录和页表把线性地址转换成一个物理地址,然后在该内存位置上执行所要求的操作(读或写)。

如果当前被访问的页面不在物理内存中,处理器就会中断程序的执行(通过产生一个页错误异常)。然后操作系统就可以从硬盘上把该页面读入物理内存中,并继续执行刚才被中断的程序。当操作系统严格实现了分页机制时,那么对于正确执行的程序来说页面在物理内存和硬盘之间的交换就是透明的。

80x86的分页机制最适合支持虚拟存储技术。分页机制会使用大小固定的内存块,而分段管理则使用了大小可变的块来管理内存。无论在物理内存中还是在硬盘上,分页使用固定大小的块更为适合管理物理内存。另一方面,分段机制使用大小可变的块更适合处理复杂系统的逻辑分区。可以定义与逻辑块大小匹配的内存单元而无需受到固定大小页面的限制。每个段都可以作为一个单元来处理,从而简化了段的保护和共享操作。

分段和分页是两种不同的地址变换机制,它们都对整个地址变换操作提供独立的处理阶段。尽管两种机制都使用存储在内存中的变换表,但所用的表结构不同。实际上,段表存储在线性地址空间,而页表则保存在物理地址空间。因而段变换表可由分页机制重新定位而无需段机制的信息或合作。段变换机制把虚拟地址(逻辑地址)变换成线性地址,并且在线性地址空间中访问自己的表,但是并不知晓分页机制把这些线性地址转换到物理地址的过程。类似地,分页机制也不知道程序产生地址的虚拟地址空间。分页机制只是简单地把线性地址转换成物理地址,并且在物理内存中访问自己的转换表。

保护

80x86支持两类保护。其一是通过给每个任务不同的虚拟地址(逻辑地址)空间来完全隔离各个任务。这是通过给每个任务逻辑地址到物理地址不同的变换映射来做到的。另一个保护机制对任务进行操作,以保护操作系统内存段和处理器特殊系统寄存器不被应用程序访问。

1.任务之间的保护

保护的一个重要方面是提供应用程序各任务之间的保护能力。80x86使用的方法是通过把每个任务放置在不同的虚拟地址空间中,并给予每个任务不同的逻辑地址到物理地址的变换映射。每个任务中的地址变换功能被定义成一个任务中的逻辑地址映射到物理内存的一部分区域,而另一个任务中的逻辑地址映射到物理内存中的不同区域中。这样,因为一个任务不可能生成能够映射到其他任务逻辑地址对应使用的物理内存部分,所以所有任务都被隔绝开了。只需给每个任务各自独立的映射表,每个任务就会有不同的地址变换函数。在80x86中,每个任务都有自己的段表和页表。当处理器切换去执行一个新任务时,任务切换的关键部分就是切换到新任务的变换表。

通过在所有任务中安排具有相同的虚拟到物理地址映射部分,并且把操作系统存储在这个公共的虚拟地址空间部分,操作系统可以被所有任务共享。这个所有任务都具有的相同虚拟地址空间部分被称为全局地址空间(Global Address Space)。这也正是现代Linux操作系统使用虚拟地址空间的方式。

每个任务唯一的虚拟地址空间部分被称为局部地址空间(Local Address Space)。局部地址空间含有需要与系统中其他任务区别开的私有的代码和数据。由于每个任务中具有不同的局部地址空间,因此两个不同任务中对相同虚拟地址处的引用将转换到不同的物理地址处。这使得操作系统可以给予每个任务的内存相同的虚拟地址,但仍然能隔绝每个任务。另一方面,所有任务在全局地址空间中对相同虚拟地址的引用将被转换到同一个物理地址处。这给公共代码和数据(如操作系统)的共享提供了支持。

2.特权级保护

在一个任务中,定义了4个执行特权级(Privilege Levels),用于依据段中含有数据的敏感度以及任务中不同程序部分的受信程度,来限制对任务中各段的访问。最敏感的数据被赋予了最高特权级,它们只能被任务中最受信任的部分访问。不太敏感的数据被赋予较低的特权级,它们可以被任务中较低特权级的代码访问。

特权级用数字0~3表示,其中0具有最高特权级;而3则是最低特权级。每个内存段都与一个特权级相关联。这个特权级限制具有足够特权级的程序来访问一个段。我们知道,处理器从CS寄存器指定的段中取得和执行指令,当前特权级(Current Privilege Level)即CPL就是当前活动代码段的特权级,并且它定义了当前所执行程序的特权级别。CPL确定了哪些段能够被程序访问。

每当程序企图访问一个段时,当前特权级就会与段的特权级进行比较,以确定是否有访问许可。在给定CPL级别上执行的程序允许访问同级别或低级别的数据段。任何对高级别段的引用都是非法的,并且会引发一个异常来通知操作系统。

每个特权级都有自己的程序栈,以避免使用共享栈带来的保护问题。当程序从一个特权级切换到另一个特权级上执行时,堆栈段也随之改换到新级别的堆栈中。

 段的定义

在上一节概述中已经提到,保护模式中80x86 提供了4GB的物理地址空间。这是处理器在其地址总线上可以寻址的地址空间。这个地址空间是平坦的,地址范围从0到0xFFFFFFFF。这个物理地址空间可以映射到读写内存、只读内存以及内存映射I/O中。分段机制就是把虚拟地址空间中的虚拟内存组织成一些长度可变的称为段的内存块单元。80386虚拟地址空间中的虚拟地址(逻辑地址)由一个段部分和一个偏移部分构成。段是虚拟地址到线性地址转换机制的基础。每个段由以下几个参数定义:

(1)段基地址(Base Address):指定段在线性地址空间中的开始地址。基地址是线性地址,对应于段中偏移0处。

(2)段限长(Limit):是虚拟地址空间中段内最大可用偏移位置。它定义了段的长度。

(3)段属性(Attributes):指定段的特性。例如该段是否可读、可写或可作为一个程序执行;段的特权级等。

段限长定义了在虚拟地址空间中段的大小。段基址和段限长定义了段所映射的线性地址范围或区域。段内0到limit的地址范围对应线性地址中范围Base到Base+Limit。偏移量大于段限长的虚拟地址是无意义的,如果使用则会导致异常。另外,若访问一个段并没有得到段属性许可则也会导致异常。例如,如果你试图写一个只读的段,那么80386就会产生一个异常。另外,多个段映射到线性地址中的范围可以部分重叠或覆盖,甚至完全重叠,如图4-6所示。在本书介绍的Linux 0.1x系统中,一个任务的代码段和数据段的段限长相同,并被映射到线性地址完全相同而重叠的区域上。

 
图4-6  虚拟(逻辑)地址空间中的段映射到线性地址空间

段的基地址、段限长以及段的保护属性存储在一个称为段描述符(Segment Descriptor)的结构项中。在逻辑地址到线性地址的转换映射过程中会使用这个段描述符。段描述符保存在内存中的段描述符表(Descriptor Table)中。段描述符表是包含段描述符项的一个简单数组。前面介绍的段选择符即用于通过指定表中一个段描述符的位置来指定相应的段。

即使利用段的最小功能,使用逻辑地址也能访问处理器地址空间中的每个字节。逻辑地址由16位的段选择符和32位的偏移量组成,如图4-7所示。段选择符指定字节所在的段,而偏移量指定该字节在段中相对于段基地址的位置。处理器会把每个逻辑地址转换成线性地址。线性地址是处理器线性地址空间中的32位地址。与物理地址空间类似,线性地址空间也是平坦的4GB地址空间,地址范围从0到0xFFFFFFFF。线性地址空间中含有为系统定义的所有段和系统表。

 
图4-7  逻辑地址到线性地址的变换过程

为了把逻辑地址转换成一个线性地址,处理器会执行以下操作:

(1)使用段选择符中的偏移值(段索引)在GDT或LDT表中定位相应的段描述符(仅当一个新的段选择符加载到段寄存器中时才需要这一步)。

(2)利用段描述符检验段的访问权限和范围,以确保该段是可访问的并且偏移量位于段界限内。

(3)把段描述符中取得的段基地址加到偏移量上,最后形成一个线性地址。

如果没有开启分页,那么处理器直接把线性地址映射到物理地址,即线性地址被送到处理器地址总线上。如果对线性地址空间进行了分页处理,那么就会使用二级地址转换把线性地址转换成物理地址。页转换将在稍后进行说明。

 

段描述符表

段描述符表是段描述符的一个数组,如图4-8所示。描述符表的长度可变,最多可以包含8192个8字节描述符。有两种描述符表:全局描述符表GDT(Global Descriptor Table)和局部描述符表LDT(Local Descriptor Table)。

 
图4-8  段描述符表结构

描述符表存储在由操作系统维护着的特殊数据结构中,并且由处理器的内存管理硬件来引用。这些特殊结构应该保存在仅由操作系统软件访问的受保护的内存区域中,以防止应用程序修改其中的地址转换信息。虚拟地址空间被分割成大小相等的两半。一半由GDT来映射变换到线性地址,另一半则由LDT来映射。整个虚拟地址空间共含有214个段:一半空间(即213个段)是由GDT映射的全局虚拟地址空间,另一半是由LDT映射的局部虚拟地址空间。通过指定一个描述符表(GDT或LDT)以及表中描述符号,我们就可以定位一个描述符。

当发生任务切换时,LDT会更换成新任务的LDT,但是GDT并不会改变。因此,GDT所映射的一半虚拟地址空间是系统中所有任务共有的,但是LDT所映射的另一半则在任务切换时被改变。系统中所有任务共享的段由GDT来映射。这样的段通常包括含有操作系统的段以及所有任务各自的包含LDT的特殊段。LDT段可以想象成属于操作系统的数据。

图4-9表明一个任务中的段如何能在GDT和LDT之间分开。图中共有6个段,分别用于两个应用程序(A和B)以及操作系统。系统中每个应用程序对应一个任务,并且每个任务有自己的LDT。应用程序A在任务A中运行,拥有LDTA,用来映射段CodeA和DataA。类似地,应用程序B在任务B中运行,使用LDTB来映射CodeB和DataB段。包含操作系统内核的两个段CodeOS和DataOS使用GDT来映射,这样它们可以被两个任务所共享。两个LDT段:LDTA和LDTB也使用GDT来映射。

 
图4-9  任务所用的段类型

当任务A在运行时,可访问的段包括LDTA映射的CodeA和DataA段,加上GDT映射的操作系统的段CodeOS和DataOS。当任务B在运行时,可访问的段包括LDTB映射的CodeB和DataB段,加上GDT映射的段。

这个例子通过让每个任务使用不同的LDT,演示了虚拟地址空间如何能够被组织成隔离每个任务。当任务A在运行时,任务B的段不是虚拟地址空间的部分,因此任务A没有办法访问任务B的内存。同样地,当任务B运行时,任务A的段也不能被寻址。这种使用LDT来隔离每个应用程序任务的方法,正是关键保护需求之一。

每个系统必须定义一个GDT,并可用于系统中所有程序或任务。另外,可选定义一个或多个LDT。例如,可以为每个运行任务定义一个LDT,或者某些或所有任务共享一个LDT。

GDT本身并不是一个段,而是线性地址空间中的一个数据结构。GDT的基线性地址和长度值必须加载进GDTR寄存器中。GDT的基地址应该进行内存8字节对齐,以得到最佳处理器性能。GDT的限长以字节为单位。与段类似,限长值加上基地址可得到最后表中最后1字节的有效地址。限长为0表示有1个有效字节。因为段描述符总是8字节长,因此GDT的限长值应该设置成总是8的倍数减1(即8n-1)。

处理器并不使用GDT中的第1个描述符。把这个"空描述符"的段选择符加载进一个数据段寄存器(DS、ES、FS或GS)并不会产生一个异常,但是若使用这些加载了空描述符的段选择符访问内存时就肯定会产生一般保护性异常。通过使用这个段选择符初始化段寄存器,那么意外引用未使用的段寄存器肯定会产生一个异常。

LDT表存放在LDT类型的系统段中。此时GDT必须含有LDT的段描述符。如果系统支持多LDT的话,那么每个LDT都必须在GDT中有一个段描述符和段选择符。一个LDT的段描述符可以存放在GDT表的任何地方。

访问LDT需使用其段选择符。为了在访问LDT时减少地址转换次数,LDT的段选择符、基地址、段限长以及访问权限需要存放在LDTR寄存器中。

当保存GDTR寄存器内容时(使用SGDT指令),一个48位的"伪描述符"被存储在内存中。为了在用户模式(特权级3)避免对齐检查出错,伪描述符应该存放在一个奇字地址处(即 地址 MOD 4 = 2)。这会让处理器先存放一个对齐的字,随后是一个对齐的双字(4字节对齐处)。用户模式程序通常不会保存伪描述符,但是可以通过使用这种对齐方式来避免产生一个对齐检查出错的可能性。当使用SIDT指令保存IDTR寄存器内容时也需要使用同样的对齐方式。然而,当保存LDTR或任务寄存器(分别使用SLTR或STR指令)时,伪描述符应该存放在双字对齐的地址处(即 地址 MOD 4 = 0)。

总结一下,GDT、GDTR、LDT、LDTR的理解:

 

1.GDTR和LDTR如何把虚拟地址转换线性地址



GDTR是一个48位寄存器,指向全局描述符表GDT,从16位到47位前32位表示GDT在内存中的地址,是线性地址,需要通过页表转换成物理地址
LDTR是一个16位寄存器,是在GDT中的索引,指向局部描述符表LDT,每个任务有一个LDT,不同的LDT占用不同的内存段,由不同的系统描述符描述,这些系统描述符放在GDT中.
LDTR里面保存的是索引值,指向LDT在GDT中的位置

如果手头上有虚拟地址xxxx:yyyyyyyy
首先从GDTR中取出GDT的基址BA找到GDT
xxxx一共16位,根据倒数第三位即T1位判断
如果T1=0,xxxx的前13位表示的是GDT的位置索引,根据索引得到一个描述符
该描述符含有段的基址与其他各种信息,段的起始地址+yyyyyyyyy就得到线程地址

如果T1=1,那么从LDTR得到LDT的位置索引,在GDT里面找到LDT描述符,LDT描述符里面包含LDT的线性地址
找到LDT,取出xxxx的前13位,在LDT中找到段描述符,该段描述符里面包含段的基址等信息.
而后段的基址加上yyyyyyyy得到线性地址

2.为什么要有一个GDTR,并且GDTR的结构和LDTR不一样呢?
这主要是因为系统只有一个GDT,而GDT的描述符有不能存放在GDT中(LDT的描述符都存放在GDT中),所以就需要一个GDTR来指示GDT在内存中的位置。因为GDTR是直接指示内存地址,而LDTR主要指示LDT描述符在GDT中的位置和属性,所以GDTR和LDTR的结构也不同。

3.实际上,每个任务的局部描述符表LDT作为系统的一个特殊段,由一个描述符描述。而用于描述符LDT的描述符存放在GDT中。在初始化或任务切换过程中,把描述符对应任务LDT的描述符的选择子装入LDTR,处理器根据装入LDTR可见部分的选择子,从GDT中取出对应的描述符,并把LDT的基地址、界限和属性等信息保存到LDTR的不可见的高速缓冲寄存器中。随后对LDT的访问,就可根据保存在高速缓冲寄存器中的有关信息进行合法性检查。


 段选择符

段选择符(或称段选择子)是段的一个16位标识符,如图4-10所示。段选择符并不直接指向段,而是指向段描述符表中定义段的段描述符。段选择符的3个字段分别是:

请求特权级RPL(Requested Privilege Level)。

表指示标志TI(Table Index)。

索引值(Index)。

 
图4-10  段选择符结构

请求特权级字段RPL提供了段保护信息,将在后面作详细说明。表索引字段TI用来指出包含指定段描述符的段描述符表GDT或LDT。TI=0表示描述符在GDT中;TI=1表示描述符在LDT中。索引字段给出了描述符在GDT或LDT表中的索引项号。可见,选择符通过定位段表中的一个描述符来指定一个段,并且描述符中包含访问一个段的所有信息,如段的基地址、段长度和段属性。

例如,图4-11a中选择符(0x08)指定了GDT中具有RPL=0的段1,其索引字段值是1,TI位是0,指定GDT表。图4-11b中选择符(0x10)指定了GDT中具有RPL=0的段2,其索引字段值是2,TI位是0,指定GDT表。图4-11c中选择符(0x0f)指定了LDT中具有RPL=3的段1,其索引字段值是1,TI位是1,指定LDT表。图4-11d中选择符(0x17)指定了LDT中具有RPL=3的段2,其索引字段值是2,TI位是1,指定LDT表。实际上,图中的前4个选择符a~d分别就是Linux 0.1x内核的内核代码段、内核数据段、任务代码段和任务数据段的选择符。图4-11e中的选择符(0xffff)指定LDT表中RPL=3的段8191。其索引字段值是0b1111111111111(即8191),TI位等于1,指定LDT表。

 
(点击查看大图)图4-11  段选择符示例

另外,处理器不使用GDT表中的第1项。指向GDT该项的选择符(即索引值为0,TI标志为0的选择符)用作"空选择符",如图4-11f所示。当把空选择符加载到一个段寄存器(除了CS和SS以外)中时,处理器并不产生异常。但是当使用含有空选择符的段寄存器用于访问内存时就会产生异常。当把空选择符加载到CS或SS段寄存器中时将会导致一个异常。

对应用程序来说段选择符是作为指针变量的一部分而可见,但选择符的值通常是由链接编辑器或链接加载程序进行设置或修改,而非应用程序。

为减少地址转换时间和编程复杂性,处理器提供可存放最多6个段选择符的寄存器(如图4-12所示),即段寄存器。每个段寄存器支持特定类型的内存引用(代码、数据或堆栈)。原则上执行每个程序都起码需要把有效的段选择符加载到代码段(CS)、数据段(DS)和堆栈段(SS)寄存器中。处理器还另外提供3个辅助的数据段寄存器(ES、FS和GS),以便当前执行程序(或任务)能够访问其他几个数据段。

 
图4-12  段寄存器结构

对于访问某个段的程序,必须已经把段选择符加载到一个段寄存器中。因此,尽管一个系统可以定义很多的段,但同时只有6个段可供立即访问。若要访问其他段就需要加载这些段的选择符。

另外,为了避免每次访问内存时都去引用描述符表,去读和解码一个段描述符,每个段寄存器都有一个"可见"部分和一个"隐藏"部分(隐藏部分也被称为"描述符缓冲"或"影子寄存器")。当一个段选择符被加载到一个段寄存器可见部分中时,处理器也同时把段选择符指向的段描述符中的段地址、段限长以及访问控制信息加载到段寄存器的隐藏部分中。缓冲在段寄存器(可见和隐藏部分)中的信息使得处理器可以在进行地址转换时不再需要花费时间从段描述符中读取基地址和限长值。

由于影子寄存器含有描述符信息的一个副本,因此操作系统必须确保对描述符表的改动反映在影子寄存器中。否则描述符表中一个段的基地址或限长被修改过,但改动却没有反映到影子寄存器中。处理这种问题最简捷的方法是在对描述符表中的描述符作过任何改动之后就立刻重新加载6个段寄存器。这将把描述符表中的相应段信息重新加载到影子寄存器中,并为加载段寄存器,提供了两类加载指令:

(1)像MOV、POP、LDS、LES、LSS、LGS以及LFS,这些指令显式地直接引用段寄存器。

(2)隐式加载指令,例如使用长指针的CALL、JMP和RET指令、IRET、INTn、INTO和INT3等指令。这些指令在操作过程中会附带改变CS寄存器(和某些其他段寄存器)的内容。

MOV指令当然也可以用于把段寄存器可见部分内容存储到一个通用寄存器中。

 

 

posted on 2009-04-10 01:03  jasonM  阅读(422)  评论(0编辑  收藏  举报