5.1 分段地址转换(Segment Translation)
图5-2详细显示了处理器如何把逻辑地址转换为线性地址。
为了这样的转换,处理器用到了以下的数据结构:
1、 描述符(Descriptors)
2、 描述符表(Descriptor tables)
3、 选择子(Selectors)
4、 段寄存器(Segment Registers)
5.1.1 描述符(Descriptors)
段描述符是处理器用来把逻辑地址映射为线性地址的必要数据结构。描述符是由编译器、连接器、加载器、或者是操作系统生成的,不能由应用程序员生成。图5-3显示了两种常用的描述符的格式。所有的段描述符都是这两种格式当中的一种。段描述符的字段是以下:
基址(BASE):决定了一个段在4G线性地址空间中的位置。处理器把3部分基址联接在一起,来形成一个32位的基址。
界限(LIMIT):决定了一个段的大小。处理器用2部分的界限字段来形成一个20位的界限值。处理器以两种方式来解析界限的值,解析方式取决于粒度位的设置情况:
1、 当单元大小为一个字节时,则定义了一个最大为1M字节的段。
2、 如果单元大小为4K字节,则段大小可以高达4G。界限值在使用之前处理器将会把它先左移12位,低12位则自动插入0。
粒度位(Granularity bit):决定了界限值被处理器解析的方式。当它被复位时,界限值被解析为以1字节为一个单元。当它置位时,则界限值以4K为一个单元。
类型(TYPE):用于区别不同类型的描述符。
描述符特权级(Descriptor Privilege Level)(DPL):用来实现保护机制(参看第六章)。
段存在位(Segment-Present bit):如果这一位为0,则此描述符为非法的,不能被用来实现地址转换。如果一个非法描述符被加载进一个段寄存器,处理器会立即产生异常。图5-4显示了当存在位为0时,描述符的格式。操作系统可以任意的使用被标识为可用(AVAILABLE)的位。一个实现基于段的虚拟内存的操作系统可以在以下情况下来清除存在位:
1、 当这个段的线性地址空间并没有完全被分页系统映射到物理地址空间时。
2、 当段根本没有在内存里时。
已访问位(Accessed bit):当处理器访问该段时,将自动设置访问位。也就是说,当一个指向该段描述符的选择子被加载进一个段寄存器时或者当被一条选择子测试指令使用时。在段级基础上实现虚拟内存的操作系统可能会周期性的测试和清除该位,从而监视一个段的使用情况。
创建和维拟描述符是系统软件的任务,一般说来可能是由,编译器、程序加载器、系统生成器、或者操作系统来协作完成的。
5.1.2 描述符表(Descriptor Tables)
段描述符存储在以下两种描述符表当中的一个:
1、 全局描述符表(GDT)
2、 一个局部描述符表(LDT)
就象图5-5所示的一样,一个描述符表仅仅是一个包含了很多描述符的8字节内存数组而以。描述符表是长度是可变的,最多可包含高达8192(2^13)个描述符。便是处理器是不会使用全局描述符表的第一项(INDEX=0)的。
处理器用GDTR和LDTR来定位内存中的全局描述符表和当前的局部描述符表。这些寄存器存储了这些表的线性地址的基址和段长界限。指令LGDT和SGDT是用业访问全局描述符表寄存器的,而指令LLDT和SLDT则是用来访问局部描述符表寄存器的。
5.1.3 选择子(Selectors)
线性地址部分的选择子是用来选择哪个描述符表和在该表中索引一个描述符的。选择子可以做为指针变量的一部分,从而对应用程序员是可见的,但是一般是由连接加载器来设置的。图5-6显示了选择子的格式。
索引(Index):在描述符表中从8192个描述符中选择一个描述符。处理器自动将这个索引值乘以8(描述符的长度),再加上描述符表的基址来索引描述符表,从而选出一个合适的描述符。
表指示位(Table Indicator):选择应该访问哪一个描述符表。0代表应该访问全局描述符表(GDT),1代表应该访问局部描述符表。
请求特权级(Requested Privilege Level):保护机制使用该位(参看第六章)。
由于全局描述符表的第一项是不被处理器使用的,所以当一个选择子的索引(Index)部分和表指示位(Table Indicator)都为0的时候(也就是说,选择子指向全局描述符表的第一项时),可以当做一个空的选择子。当一个段寄存器被加载一个空选择子时,处理器并不会产生一个异常。但是,当用一个空选择子去访问内存时,则会产生异常。这个特点可以用来初始化不用的段寄存器,以防偶然性的非法访问。
5.1.4 段寄存器(Segment Registers)
80386把描述符的信息存储在段寄存器里,以便不用每次内存访问都去访问内存中的描述符表。
如图5-7所示,每一个段寄存器都有一个可见部分和一个不可见部分。这些段寄存器的可见部分被程序员当作一个16位的寄存器来使用。不可见的部分则只能由处理器来操纵。
加载这些寄存器的操作和一般的加载指令是一样的(和第三章讲述的相同),这些指令分为两类:
1、 真接的加载指令,例如,MOV,POP,LDS,LSS,LGS,LFS。这些指令显示的访问这些段寄存器。
2、 隐式的加载指令,例如,far CALL和JMP。这些指令隐式的访问CS 段寄存器,将它加载一个新的值。
使用这些指令,程序将用一个16位的选择子加载段寄存器的可见部分。处理器将自动的将基址、界限、类型、和其它信息从描述符表中加载到段选择子的不可见部分。
因为很多数据访问的指令都是访问一个已加载段寄存器的数据段,所以处理器可以用与段相关的基址部分加上指令提供的偏移部分,而且不会有额处的加法开销。