全局描述符表GDT和局部描述符表LDT

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/qq_37653144/article/details/82821540

段描述符

       IA-32架构的处理器访问内存都是采用“段基址:段内偏移地址”的形式,即使到了保护模式也不例外。其次,实模式脆弱的安全性也是保护模式推出的重要原因。为了内存安全性,必须为内存段添加一些额外的安全属性,如特权级、段界限、段类型等。描述内存段属性的数据结构就叫段描述符。其结构定义如下所示:

       一个段描述符是8字节大小,它描述了一个内存段的地址范围和各种属性。

       保护模式下地址总线宽度是32位,因此段基址需要用32位地址来表示。从上图看出段基址字段一共有2个部分,分别在第2~4字节和第7字节。这样划分的目的是为了兼容旧式的16位CPU(段界限字段也是如此)。

       段界限表示段边界的扩展极值,即最大扩展到多少或最小扩展到多少。扩展方向只有上下两种,对于数据段和代码段,段的扩展方向是向上,即从低地址向高地址扩展,此时的段界限用来表示段内偏移的最大值(上界);对于栈段,段的扩展方向是向下,即从高地址向低地址扩展,此时的段界限表示段内偏移的最小值(下界)。无论是向上还是向下,段界限都表示段的边界。段界限字段给出的只是数值,其单位(或称粒度)则在G位中给出,G位为0则粒度为B,为1则为4KB。因此段界限边界值的计算公式为:

                                       (段界限字段值+1)*(粒度大小)- 1

       内存访问需要用到“段基址:段内偏移地址”,段界限其实是用来限制段内偏移地址的,段内偏移地址必须位于段描述符给出的范围之内,否则CPU会抛出异常。任何超范围的偏移地址都被认为是非法的,CPU会捕获这个异常。

       属性字段中的type字段用来指定段描述符的类型,而S位的数值决定了type字段中不同位的含义。一个段描述符首先分为两大类,要么是系统段(S位置0),要么是非系统段(S位置1),或称数据段。对于CPU而言,凡是硬件运行需要用到的东西都可称之为系统(如硬件在内存中的映射),凡是软件需要用到的东西(操作系统也是软件,对CPU而言在这一层面它与用户程序无区别)都是数据。无视是代码还是数据,都是作为硬件的输入,因此我们常说的代码段在段描述符中也属于数据段(非系统段)。type字段要和S字段配合才能确定段描述符的确切类型,只有S字段的值确定后type字段的值才有意义。

段描述符的type类型
系统段 系统段类型 第3~0位 说明
3 2 1 0
未定义 0 0 0 0 保留
可用的80826 TSS 0 0 0 1 仅限286的状态段
LDT 0 0 1 0 局部描述符表
忙碌的80826 TSS 0 0 1 1 仅限286,type中的第1位称为B位,若为1,则表示当前任务忙碌,由CPU将此位置1
80826调用门 0 1 0 0 仅限286
任务门 0 1 0 1 任务门标识(现代操作系统中很少用到)
80826中断门 0 1 1 0 仅限286
80826陷阱门 0 1 1 1 仅限286
未定义 1 0 0 0 保留
可用的80836 TSS 1 0 0 1 386及以上的CPU的TSS
未定义 1 0 1 0 保留
忙碌的80836 TSS 1 0 1 1 386及以上的CPU的TSS
80836调用门 1 1 0 0 386及以上的CPU的调用门
未定义 1 1 0 1 保留
中断门 1 1 1 0 386及以上的CPU的中断门
陷阱门 1 1 1 1 386及以上的CPU的陷阱门
对于非系统段,按代码段和数据段划分,这4位分别由不同的意义
非系统段 内存段类型

X

R C A 说明
代码段 1 0 0 * 只执行代码段
1 1 0 * 可执行、可读代码段
1 0 1 * 可执行、一致性代码段
1 1 1 1 可执行、可读、一致性代码段
数据段

X

W

R

A 说明
0 0 0 * 只读数据段
0 1 0 * 可读写数据段
0 0 1 * 只读,向下扩展的数据段
0 1 1 * 可读写,向下扩展的数据段

       表中的A表示Accessed,由CPU来设置,每当该段被CPU访问过后,CPU将该段的段描述符中的A位置1。

       C表示一致性(Conforming)代码段,也称为依从代码段。与访问权限有关,C为1时表示该段是一致性代码段,为0时则表示改段是非一致性代码段。

       R即Read,为1表示可读,为0则表示不可读。这个属性一般用来限制指令对代码段的访问,对于CPU而言,这个标志位不起作用,也就是说即使R为0,CPU一样可以访问该段。

       X即Executable,用来标识该段是否可执行。

       E即Extend,用来表示段的扩展方向,0表示向上扩展(从低地址到高地址),1表示向下扩展(从高地址到低地址)。

       W即Writable,用来表示段是否可写。

       DPL(Descriptor Privilege Level)字段为描述符特权级,这是保护模式提供的安全解决方案,有4个不同等级(0~3,数值越小特权级越大)。

       AVL即Available,表示段是否可用(对CPU无效)。

       L字段用来设置是否是64位代码段,L为1表示64位代码段,为0表示32位代码段。

       D/B字段用来表示有效地址(段内偏移地址)及操作数的大小。对于代码段来说,此位是D位,若D为0,表示指令中的有效地址和操作数是16位,使用ip寄存器;否则为32位,使用eip寄存器。对于栈段来说,此位是B位,用来指定操作数大小,若B为0,使用的是sp寄存器,也就是栈的起始地址是16位寄存器的最大寻址范围,0xFFFF;若B为1则使用esp寄存器,即栈的起始地址是32位寄存器的最大寻址范围,0xFFFFFFFF。

全局描述符表

       全局描述符表(Global Descriptor Table,GDT)是保护模式所必须的数据结构,引入GDT主要是出于系统安全性、内存寻址方式的兼容等方面的考虑。

       一个段描述符只用来定义一个内存段,这些描述符都存放在全局描述符表中。GDT相当于存放段描述符的数组,而索引则是选择子。全局体现在多个程序都可以在GDT中定义自己的段描述符,是公用的,全局可见。GDT在内存中,需要用专门的寄存器GDTR(Global Descriptor Table Register)指引CPU找到GDT。GDTR是48位的寄存器,低16位存放GDT界限,剩余32位存放GDT起始地址。

选择子

       在实模式中,段寄存器存放的是段基地址,而在保护模式下段基址已经存放在段描述符中了。因此,段寄存器的作用就改为了存储全局描述符表的索引——选择子。

       由于段寄存器是16位的,所以选择子也是16位。在其低2位,即第0~1位用来存储RPL(Requested Privilege Level),请求特权级。选择子的第2位是TI(Table Indicator)位,用来表示选择子是在GDT中,还是LDT中,该位为1表示在GDT中,为0表示在LDT中。选择子的高13位,即第3~15位为描述符表的索引值。由于索引值为13位,因此最多可以索引8192个段描述符,这与GDT中最多定义8192个段描述符是吻合的。

局部描述符表

       局部描述符表(Local Descriptor Table,LDT)是CPU厂商为在硬件一级原生支持多任务而创建的表,按照设想,一个任务对应一个LDT。但现代操作系统中很少使用LDT。

       LDT的设计厂商建议每个任务的私有内存段都应该放到自己的段描述符表中,即每个任务都要有自己的LDT,随着任务的切换,也要切换到相应任务的LDT。LDT需要先在GDT中注册一个描述符,与GDT不同的是,LDT的第0个段描述符是可用的,因为选择子中的TI位为1才表示在LDT中索引段描述符,TI为1必然是经过显式初始化的结果。

A20地址线

       实模式下有地址回绕机制,由于实模式下的地址线是20位,最大寻址空间为0x00000~0xFFFFF,超出1MB的部分在逻辑上也是正常的,CPU将超出部分自动回绕到0地址,相当于对1MB求模。

       在8086/8088中,只有20位地址线,即A0~A19,若内存超过1MB,需要第21位地址线支持。对于80286后续的CPU,通过A20Gate来控制A20地址线。为了兼容,后面出现的CPU即使地址线已经多出很多,但依然保留了A20Gate,需要开启它才能访问超过1MB的内存空间。

猜你喜欢

转载自blog.csdn.net/qq_37653144/article/details/82821540