GDT(Global Descriptor Table)全局描述符表

GDT是一个以 段描述符 为表项的数组类型的数据结构,在内存中线性存放。每个段描述符占 8 个字节。

GDTR寄存器存放GDT表的基地址和表长界限(高32位存放GDT基址,低16为存放GDT限长)。 指令LGDT和SGDT分别用于加载和保存GDTR寄存器的内容。在机器刚加电或处理器复位后,基地址被默认地设置为0,而长度值被设置成0xFFFF。在保护模式初始化过程中必须给GDTR加载一个新值。

段选择子(Segement Selector)

由GDTR访问全局描述符表是通过“段选择子”(实模式下的段寄存器)来完成的。段选择子是一个16位的寄存器(同实模式下的段寄存器相同)。

段选择子共16bit,由三部分组成: 描述符索引(index)、TI、请求特权级(RPL)

  • 描述符索引(index): 在描述符表中定位一个段描述符。
  • TI: 描述符表指示器, TI=0 时,表示描述符在 GDT 中; TI=1 时,描述符在 LDT 中。
  • RPL: 请求特权级,表示给出当前选择子访问的内存段的特权级别。每一个段都有一个特定的级别。每当一个程序试图访问某一个段时,就将该程序所拥有的特权级与要访问的特权级进行比较,以决定能否访问该段。系统约定,CPU只能访问同一特权级或级别较低特权级的段。

因为每个描述符占 8 字节,因此描述符在表内的偏移地址是索引号乘以 8

处理器在执行任何改变段选择器的指令时(比如 pop、 mov、jmp far、 call far、 iret、 retf),就将指令中提供的索引号乘以 8 作为偏移地址,同 GDTR 中提供的线性基地址相加,以访问 GDT。 在表内找到的描述符,并加载到不可见的描述符高速缓存部分。此后每当有访问内存的指令时,就不再访问 GDT 中的描述符,直接用当前段寄存器描述符高速缓存器提供线性基地址。

段描述符(Segment Descriptor)

每个段描述符占 8 个字节。由三部分组成: 线性基地址、段界限和段的访问属性。

  • G: 粒度位,用于解释段界限的含义。当 G 位是"0"时,段界限以字节为单位。此时,段的扩展范围是从 1 字节到 1 兆字节( 1B~1MB),因为描述符中的界限值是 20 位的。 相反,如果该位是"1",那么,段界限是以 4KB 为单位的。这样,段的扩展范围是从 4KB到 4GB。

  • D/B: “默认的操作数大小”(Default Operation Size)或者"默认的堆栈指针大小",又或者"上部边界"标志。 设立该标志位,主要是为了能够在 32 位处理器上兼容运行 16 位保护模式的程序。D=0 表示指令中的偏移地址或者操作数是 16 位的; D=1,指示 32 位的偏移地址或者操作数。

  • L: 64 位代码段标志。保留此位给 64 位处理器使用。32位将此位置"0"。

  • AVL: 保留位。可以被系统软件使用。

  • P: 段存在位(Segment Present)。 P 位用于指示描述符所对应的段是否存在。 一般来说,描述符所指示的段都位于内存中。但是,当内存空间紧张时,有可能只是建立了描述符,对应的内存空间并不存在,这时,就应当把描述符的 P 位清零,表示段并不存在。P 位是由处理器负责检查的。 每当通过描述符访问内存中的段时,如果 P 位是"0",处理器就会产生一个异常中断。

  • DPL: 描述符的特权级(Descriptor Privilege Level, DPL)。这两位用于指定段的特权级。 共有 4 种处理器支持的特权级别,分别是 0、 1、 2、 3,其中 0 是最高特权级别, 3 是最低特权级别。刚进入保护模式时执行的代码具有最高特权级 0(可以看成是从处理器那里继承来的),这些代码通常都是操作系统代码(内核段),因此它的特权级别最高。 每当操作系统加载一个用户程序时,它通常都会指定一个稍低的特权级(用户段),比如 3 特权级。不同特权级别的程序是互相隔离的,其互访是严格限制的,而且有些处理器指令(特权指令)只能由 0 特权级的程序来执行,为的就是安全。这里再次点明了为何叫保护模式。

  • S: 用于指定描述符的类型(Descriptor Type)。当该位是"0"时,表示是一个系统段;为"1"时,表示是一个代码段或者数据段(堆栈段也是特殊的数据段)。

  • TYPE: 共 4 位,用于指示描述符的子类型,或者说是类别。

描述符类型

对于数据段来说, 这 4 位分别是 X、 E、 W、 A 位;而对于代码段来说,这 4 位则分别是 X、 C、 R、 A 位。

数据段

  • X: 表示是否可以执行( eXecutable)。数据段总是不可执行的,X=0。
  • E: 针对数据段。E 位指示段的扩展方向。 E=0 是向上扩展的,也就是向高地址方向扩展的,是普通的数据段; E=1 是向下扩展的,也就是向低地址方向扩展的,通常是堆栈段。
  • W: 段的读写属性,W=0 的段是不允许写入的,否则会引发处理器异常中断; W=1的段是可以正常写入的。
  • A: 已访问位,用于指示它所指向的段最近是否被访问过。在描述符创建的时候,应该清零。之后,每当该段被访问时,处理器自动将该位置"1"。

代码段

  • X: 表示是否可以执行(eXecutable)。代码段总是可以执行的 X=1。

  • C: 是否为特权级依从的(Conforming)。 C=0 表示非依从的代码段,这样的代码段可以从与它特权级相同的代码段调用,或者通过门调用; C=1 表示允许从低特权级的程序转移到该段执行。

  • R: 是否允许读出。代码段总是可以执行的,但是,为了防止程序被破坏,它是不能写入的。至于是否有读出的可能,由 R 位指定。 R=0 表示不能读出,如果企图去读一个 R=0 的代码段,会引发处理器异常中断; 如果 R=1,则代码段是可以读出的,即可以把这个段的内容当成 ROM 一样使用。

    也许有人会问,既然代码段是不可读的,那处理器怎么从里面取指令执行呢?事实上,这里的R属性并非用来限制处理器, 而是用来限制程序和指令的行为。

  • A: 已访问位,用于指示它所指向的段最近是否被访问过。在描述符创建的时候,应该清零。之后,每当该段被访问时,处理器自动将该位置"1"。