Linux0.11中的分段机制

时间:2022-12-30 23:20:34

 

分段机制可以实现多种系统设计。这些设计从使用分段机制的最小功能来保护程序的平坦模型,到使用分段机制创建一个可同时可靠地运行多个程序的具有稳固操作环境的多段模型。

80386虚拟地址空间中的虚拟地址(逻辑地址)由一个段部分和一个偏移部分组成。

一、段的定义

段由三个参数定义:

1.段基地址(Base Address)

指定虚拟地址空间在线性地址空间中的开始地址。基地址是线性地址,对应于段中偏移为0的位置。

2.段限长(Limit)

是虚拟地址空间中段内的最大可用偏移地址。它定义了段的长度。

3.段属性(Attributes)

指定段的属性。例如段是否可读、可写或可执行,段的特权级等。

段的基地址、段限长以及段的属性存储在一个称为段描述符(Segment Descriptor)的结构项中。在从虚拟地址到线性地址的转换过程中会使用这个描述符。段描述符存储在内存中的段描述符表(Descriptor Table)中。

段描述符表是包含段描述符项的一个数组。我们通过段选择符指定表中的一个描述符项来指定相应的段。

二、虚拟地址(逻辑地址)到线性地址的转换。

逻辑地址由一个16位的段选择符和32位的偏移量组成。段选择符指定字符所在的段,而偏移量指定该字节在段中相对于段基地址的偏移量。

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

2.根据段描述符中对访问权限和Limit判断该段是否可以访问,偏移量是否位于段的范围内。

3.从段描述符中取出段基地址,加上虚拟地址原有的32位偏移量就形成最终的线性地址。

Linux0.11中的分段机制

 

 

 

 

 

 

 

三、段描述符表

段描述符表是段描述的数组。描述表的长度可变,最多可以包括8192个8字节的描述符。有两种描述符表:全局描述符表GDT(Global Descriptor Table),局部描述附表LDT(Local Descriptor Table)

Linux0.11中的分段机制

段描述附表存储在由操作系统维护者的特殊数据结构中,并且由处理器的内存管理硬件来引用。这些特殊结构应该保存在仅有操作系统软件访问的 受保护的内存区域中,以防止应用程序修改其中的地址转换信息。虚拟地址空间被分割成大小相等的两半。一半由GDT来映射变换到线性地址,另一半则由LDT来映射。整个虚拟地址空间共含有2的12次方个段:2的13次方个段由GDT映射的全局虚拟地址空间,另一半是由LDT映射的局部虚拟地址空间。

通过指定描述附表以及表中的段描述符号,我们就可以定位一个段描述符。

当发生任务切换时,LDT会更换成新的LDT,但是GDT不会改变。因此,GDT映射的那一半虚拟地址空间是系统中所有任务共有的,但是LDT所映射的另一半则在任务发生切换时被改变。

下图示例了一个任务中的段如何在GDT和LDT之间分开。图*有6个段,分别用于两个应用程序A和B以及操作系统。系统中每个程序对应一个任务,并且每个任务有自己的LDT。

应用程序A在任务A中运行,拥有LDTA,用来映射段CodeA和DataA。类似的,应用程序B在任务B中运行,使用LDTB映射段CdoeB和DataB段。包含操作系统内核的两个段CodeOS和DataOS使用GDT来映射,这样他们可以被两个任务共享。两个LDT段LDTA和LDTB也由GDT来映射。

Linux0.11中的分段机制

这样任务A可以访问段LDTA映射的CodeA和DataA,任务B可以访问段LDTB映射的CdoeB和DataB。两个任务A和B都可以访问由GDT映射的CodeOS和DataOS。而任务A不能访问任务B的段,通过LDT隔离了每个应用任务。

GDT表的线性基地址和长度值保存在GDTR寄存器中。由于GDT中的段描述符项长度为8个字节,因此他的线性基地址与8字节对齐,而且表限长为8N-1。而限长为0表示由一个有效字节。

LDT表保存在LDT类型的系统端中。此时,GDT中必须含有LDT段的描述符。访问LDT需要使用其段选择符。为了访问LDT时减少地址转换次数,LDT的段选择符、基地址、段限长以及访问权限需要存放在LDTR寄存器中。

四、段选择符

段选择符是短的一个16位标识符。段选择符并不直接指向段,而是指向段描述符表中的段描述符。

段选择符包括3部分内容:

  • 请求特权级RPL(Request Privilege Level)
  • 表指示标志TI(Table Index)
  • 索引值(Index)

Linux0.11中的分段机制

其中,RPL提供端保护信息。表指示标志TI=0表示段描述符在GDT中,TI=1表示段描述符在LDT中。索引值给出了段描述符在段描述符表中的索引号。

为了减少地址转换时间和编程复杂性,处理器提供最多6个存储段选择符的段寄存器。每个段寄存器支持特定类型的内存引用(代码、数据或者堆栈)。

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

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

由于描述符缓冲含有段描述符的一个拷贝,因此我们在每次段描述符表改动之后就立刻重新加载6个段寄存器。

Linux0.11中的分段机制

五、段描述符

段描述符是GDT和LDT中的数据结构项。每个段描述符是8字节,含有三个主要字段:段基地址、段限长和段属性。段描述符绝对不会由应用程序来创建。

段描述符的一般格式:

Linux0.11中的分段机制

段描述符中各个字段的含义如下:

  • 段限长字段

段限长字段用于指定段的长度。根据颗粒度标志G来指定段限长的实际含义。如果G=0,则段长范围可以从1字节到1MB,单位是字节。如果G=1,则断长范围可以从4KB到4GB,单位是4KB。

而且,根据段类型中的段扩展方向标志E,处理器以两种不同的方式处理段限长。

对于向上扩展的段,逻辑地址中的偏移值范围可以从0到段限长limit,大于段限长的偏移值将产生保护性异常。

对于向下扩展的段,逻辑地址中的偏移值范围可以从Limit到0XFFFFFFFF或0XFFFF(根据默认堆栈指针大小标志B的设置)。而小于段限长的偏移值将产生保护性异常。对于下扩段,减小段限长字段中的值会在该段地址空间底部分配新的内存,而不是在顶部分配。80x86的栈总是向下扩展的,因此这种实现方式很适合扩展堆栈。

  • 段基地址

指向段的基地址,一般来说,程序会自动将其对齐在16字节边界。

  • 段类型

指定段或门的类型、说明段的访问种类以及段的扩展方向。该字段的解释依赖于描述符类型标识S指明是一个应用描述符(代码或数据)还是一个系统描述符。TYPE字段的编码对代码、数据或系统描述符都不同。

  • 描述符类型标识

指明一个段描述符是系统段描述符(S=0)还是代码或数据段描述符(S=1)。

  • 描述符特权级

指明描述符的特权级。特权级范围从0到3,0最高,3最低

  • 段存在标志

指明段是否在内存中。P=1在内存中,P=0不在内存中。

  • D/B(默认操作大小/默认栈指针大小和/或上界限)

根据段描述符描述的是一个可执行代码段、下扩展数据段还是一个堆栈段,这个标志位具有不同的功能。对于32位代码和数据段,这个标志应该总是设置为1;对于16位代码和数据段,这个标志位被设置为0.

  1. 可执行代码段。此时这个标志称为D标志。用于指出该段中的指令应用有效地址和操作数的默认长度。如果该标志位置位,则默认值是32位之地和32位或8位的操作数;如果该标志位为0,则默认值是16位地址和16位或8位的操作数。指令前缀0x66可以用来选择非默认的操作数大小;前缀0x67可用来选择非默认的地址大小。
  2. 栈段(由SS寄存器指向数据段)。此时这个标志称为B标志。用于指明隐含堆栈操作时的栈指针大小。如果该标志置位,则使用32位栈指针并存放在ESP寄存器中;如果该标志位为0,则使用16位堆栈指针并存放在SP寄存器中。如果堆栈段被设置成一个下扩数据段,这个B标志也同时制定了堆栈段的上界限。
  3. 下扩数据段。此时该标志称为B标志,用于指明堆栈段的上界限。如果设置了该标志,则堆栈段的上界限是OXFFFFFFFF(4GB);如果没有设置该标志,则堆栈段的上界限是0XFFFF(64KB)。
  • 颗粒度标志G

该字段确定段限长字段limit的单位。如果G标志位为0,则段限长的单位是字节。如果G=1,则段限长值使用4KB单位。在设置了G标志的情况下,使用段限长来检查偏移值时,并不检查偏移值的12位最低有效位。例如,当G=1时,段限长为0标明有效偏移值0为0到4095

  • 可用和保留比特位

段描述符第2个双字的位20可供系统软件使用。位21是保留为并总为0。

Linux0.11中的分段机制

Linux0.11中的分段机制

代码段、数据段和系统段描述符格式

Linux0.11中的分段机制

P=0时段描述符格式