该篇博文主要对存储器管理涉及的一些知识点进行总结,为接下来的存储器模型博文做好准备。《32 位微型计算机接口技术及应用》一书将作为主要参考资料。
博文最后有一大图,算是上篇博文跟这篇博文的大总结吧!
段的定义
段是实现分段管理机制的基础。实模式和保护模式对段的定义是完全不同的。
- 实模式的段的定义比较单一,
段基地址以被 16 整除为边界,段长固定为 64KB,无保护机制。
- 保护模式下,(由于引入保护机制)每个段由
段基地址(Base)
、段限长(Limit)
和段属性(Attributes)
3 个要素进行定义:
- 段基地址(Base):描述段的开始地址,长 32 位。段可以从 32 位线性地址空间中的任何一个字节开始,而不像实模式下段的边界必须能够被 16 整除。
- 段限长(Limit):描述段的大小,长 20 位。每个段的长度是可变的,最大长度与粒度 G(Granularity)有关:G=0 表示以字节为单位,20 位的最大长度是 1MB,增量为 1B;G=1 表示以 4KB 位单位,20 位的最大长度是 4GB,增量为 4KB。
- 段属性(Attributes):描述段的保护属性。它是对段访问的
合法性
进行检查的主要根据。这一部分是保护模式为实现保护功能而赋予段的新的含义。段属性还可以使微处理器的保护模式支持 32 位和 16 位程序,这取决于代码段的属性。
以上对段的定义是通过对段设置描述符实现的,即段描述符就是用来描述一个段的 3 个参数的
。而段描述符需要段选择子来检索得到。整个流程如下:段选择子 -> 段描述符 -> 段的 3 个参数。
段的类型
保护模式下虚拟地址空间的段,可分为存储段、系统段和门三大类。
存储段
存储段是存储代码和数据的存储器段,包括了代码段、数据段和堆栈段
。
系统段
系统段包括了局部描述符表(Local Descriptor Table, LDT)段
和任务状态段(Task State Segment)
。这两个段是保护模式新增加的,不是用于存放代码和数据,而是作为特殊用途:LDT 段在访问某一个任务中的局部数据与代码时使用,TSS 在处理任务切换时使用。
注意:局部描述符表 LDT 跟局部描述符表‘段’是不一样的。
局部描述符表段
局部描述符表段用于存放局部描述符表 LDT。
一个任务可能涉及多个局部代码段与局部数据段,因此需要有多个局部描述符来定义这些代码段和数据段。为了管理这些局部描述符,就需要建立局部描述符表,这个表放在局部描述符表段。
取得局部描述符的流程如下(取得 LDT 描述符过程可参考上篇博文“局部描述符表寄存器”部分):LDTR
-> GDT
-> LDT 描述符
-> LDT 段
-> LDT
-> (N 个)局部描述符
任务状态段
任务状态段的作用
从实模式“保护现场”和“恢复现场”说起
在实模式的单一任务环境下,如果出现了程序转移(如硬件中断),微处理器会将‘被中断程序’中的寄存器(如 CS:EIP)保存到堆栈中,这叫保护现场;待中断服务程序完成后,再将堆栈中保存的内容或状态送回到各寄存器,以便原来的程序继续运行,这叫恢复现场。保护模式
与实模式的类似,在保护模式的多任务系统中,当发生任务切换时,被暂时中止或挂起的任务的现场信息必须完整地保存下来,否则中止结束后无法返回原来的任务继续执行。但是,保护模式下一个任务执行时所涉及的现场信息很复杂,远远不是几个寄存器就可以把它们保存下来的,所以每个任务都需要有一个空间(地方)来保存这些状态信息,把保存这些状态信息的空间叫做任务状态段 TSS。
所以,TSS 里面保存的不是代码和数据,而是一些特殊用途的现场信息。TSS 存放着一个任务的全部运行状态信息。
每个任务都有自己的 TSS。
任务状态段的信息
TSS 的内容包括了主体部分和扩展部分的信息。详细可参考《32 位微型计算机接口技术及应用》一书第 5.2.2 节。
门
门包括调用门、中断门、陷阱门和任务门。门也是保护模式新添加的。其实,门并不是段,但是,由它可以通向一个程序的入口或一个任务的入口
,而程序在存储器中表现为代码段,因此,门与代码段有关。
段描述符
在保护模式下,每一个段都用一个相应的描述符(8 字节长)来描述。可以说,一提到段就必有相应的段描述符,而一提到描述符就一定是针对相应的段来说的。
存储段描述符
存储段描述符格式如下表所示:
上表中使用不连续字段来存放段基地址和段限长的原因是为了跟 80286 兼容。
其中,存储段属性的TYPE
字段的最高位
意义:
- 0:数据段
- 1:代码段
段描述符类型指示位 DT
:
- 1:存储段描述符
- 0:系统描述符或门描述符
系统段描述符
系统段描述符格式如下表所示:
门描述符
门描述符并不描述一个内存段,而是描述程序转移的入口点,即描述目标程序的代码段的段基地址和偏移量及其属性。这种描述符提供了从一个程序代码段通向另一个程序代码段的门。通过这种门,可以调用一个程序或中断转移到一个程序,用于任务内特权级的改变和任务间的切换。
按程序控制转移的途径不同,门描述符的类型可分为:调用门描述符、任务门描述符、中断门描述符和陷阱门描述符。
门描述符的一般格式如下表所示:
跟存储段和系统段描述符(段基地址、段限长和段属性)不同的是,门描述符:
- 选择子:用来从描述符表中选择(检索)段描述符,从而得到相应的段基地址,即程序转移的入口的段基地址。
- 段偏移量:用于指定程序转移入口的段偏移量。
具体地,中断门描述符、陷阱门描述符和任务门描述符格式如下:
图片来源:《Linux 内核完全注释》
描述符表
一个任务可能会使用多个段,每个段需要一个描述符来描述。
为了便于组织管理,32 位微处理器把描述符汇集成线性表,由描述符组成的线性表称为描述符表。
描述符表本身在内存中也占用一个段。
在一个描述符表中所包含的描述符的个数根据需要确定,可多可少,但最多只能包含 8192 个(参考上一博文“全局描述符表寄存器”部分)。
全局描述符表 GDT
GDT 中所包含的描述符是每一个任务“都可访问的段”
的描述符,即可共享的段的描述符,如可包含代码段和数据段的描述符、各任务的 LDT 段的描述符、TSS 的描述符、调用门和任务门描述符等。
GDT 由伪描述符的 48 位指针(直接当成 GDTR 来理解就行了,参考上一博文“全局描述符表寄存器”部分)来描述。
GDT 的第一个描述符总是空描述符,定义时全写 0。
局部描述符表 LDT
LDT 所包含的描述符是任务自己的代码段、数据段和堆栈段的描述符,以及任务所使用的一些门描述符。
LDT 包含的描述符所描述的这些段是一个任务私有而其他任务不能共享的。
LDT 本身也是一个段,需要一个 LDT 段描述符来描述它。这个描述符存放在 GDT 中。
其余参考上一博文“局部描述符表寄存器”部分。
中断描述符表 IDT
在保护模式下,32 位微处理器不使用实模式下的中断向量表 IVT,而是使用 IDT。IDT 所包含的描述符是中断门、陷阱门和任务门描述符。也就是说,在保护模式下,32 位微处理器只有通过中断门、陷阱门或任务门才能够转移到对应的中断服务程序或异常处理程序。
值得注意的是,实模式(16 位)中断机制使用的是中断向量表 IVT,起始位置固定在 0x00000 处;保护模式(32 位)使用的是中断描述符表 IDT,位置是不固定的,由 IDTR 指定。
其余参考上一博文“中断描述符表寄存器”部分。
段选择子
实模式下存储单元的地址由段基地址和段内偏移两部分组成,而保护模式下存储单元的地址由段选择子和段内偏移两部分组成。
段选择子是一个检索描述符的索引。
当程序请求使用一个存储段时,可利用它从描述符表中选取(检索)段描述符,找到描述符后,就可以得到段基地址。因此,段选择子并不是段基地址,但通过它能够获得段基地址。
有了描述符才能确定段基地址,把段基址加上偏移量就能得到线性地址。这样一来,就把虚拟空间中的由选择子和偏移量两部分构成的二位虚拟地址映射成一维线性地址。
段选择子特指选择的是“存储段”描述符。
段选择子的格式如下表所示:
- 描述符索引 DI:指描述符在描述符表中的序号,用 13 位表示,故可检索 8192 个描述符。
- 描述符表指示位 TI: 为 0 表示从 GDT 选取描述符;为 1 表示从 LDT 中选取描述符。
- 请求特权级 RPL(Request Privilege Level):用于特权检查,实现特权级保护。
值得指出的是,在 GDT 中的第 0 个描述符总是不被微处理器访问,一般把它置成全 0,叫做空描述符。因此,当选择子 3 个字段的值分别为 DI=0,TI=0,RPL=任意值 时,并不对应于 GDT 的第 0 个描述符,而叫做空选择子。然而,但选择子 3 个字段的值分别为 DI=0,TI=1,RPL=任意值 时,就不是空选择子,而是指向当前任务 LDT 的第 0 个描述符。
大总结
最后,只想说的是,来个大总结吧(画了一晚上…累):
PS. 后来在网上看到一个画的更加不错的:
图片来源