全局描述符表

当进入保护模式之后,和一个段有关的信息需要使用8个字节来进行描述。这8个字节就被称为段描述符(Segment Descriptor)。所有的描述符在内存中连续存放,从而构成描述符表。

最主要的描述符是全局描述符表(Global Descriptor Table,GDT)。在进入保护模式之前,必须先定义全局描述符表。

全局描述符的起始地址被存储在全局描述符表寄存器GDTR中。该寄存器分为两个部分,分别是32位的线性地址和16位的边界值。
《x86汇编语言 从实模式到保护模式》学习实验笔记【10】进入保护模式
其中,全局描述符表边界即为全局描述符表界限值,大小是表内最后1字节的偏移量。第1个字节的偏移量是0,最后1字节的偏移量是表大小减1。

理论上,全局描述符表可以位于内存中的任意地址处,不必对齐。但是在进入保护模式之前我们就要设置好GDT,所以最开始GDT必须位于前1MB内存中。为简单起见,我们将GDT放在紧挨着主引导程序之后。因为主引导程序大小为512字节,起始物理地址是0x7C00,所以GDT的地址为0x7C00 + 512,即物理地址0x00007E00处,如下图所示:
《x86汇编语言 从实模式到保护模式》学习实验笔记【10】进入保护模式

段描述符

《x86汇编语言 从实模式到保护模式》学习实验笔记【10】进入保护模式
由上图可知,每一个段描述符大小是8个字节,64位。上图中下面是低4字节的内容,上面是高4字节的内容。
描述符中指定了32位的段起始线性地址,以及20位的段边界。如果未开启分页功能,那么线性地址就是物理地址。

对于20位的段界限来说,如果是向上扩展的代码段或数据段,它决定了偏移量的最大值;如果是向下扩展的堆栈段,段界限决定了偏移量的最小值。

G代表段的粒度。若G=0,则表示段界限以字节为单位,此时20位的段界限可以表示从1字节到1MB的范围;若G=1,则表示段界限以4KB为单位,此时20位的段界限可以表示从4KB到4GB的范围。

S位用于表示描述符的类型。当S=0时,表示这是一个系统段;当S=1时,表示这是一个代码段或数据段。

DPL位表示描述符的特权级(Descriptor Privilege Level,DPL)。这两位用于指定对应段的特权级,即0、1、2、3。其中0级为最高特权级别,3级为最低特权级别。刚进入保护模式时执行的代码具有最高的特权级别。通常0级用于操作系统的代码,3级用于普通用户程序的代码。有些CPU指令只能由0特权级的程序来执行。

P表示段存在位(Segment Present)。P位用于指示描述符所对应的段是否存在。当P=1时,表示段位于硬盘中;当P=0时,表示段位于内存中。

D/B位表示默认操作设大小。当为0时表示是16位的,当为1时表示是32位的。

L位是64位代码段标志,为1表示是64位。

TYPE字段共有4位,用于指示描述符的子类型,具体含义如下图所示:
《x86汇编语言 从实模式到保护模式》学习实验笔记【10】进入保护模式

由上图可知,当X位为1时,表示代码段;当X位为0时表示数据段。
对于数据段,E位指示段的扩展方向。E=0表示向上扩展,即向高地址方向扩展;E=1时表示向下扩展,即向低地址方向扩展。W位表示是否可写。若W=0表示段是不允许被写入的,否则会触发CPU异常中断,若W=1则表示段是可以正常读写的。

对于代码段,C位表示该段是否是特权级依从。C=0表示非依从段,即该代码段只能从与它特权级相同的代码段调用,或者通过门调用;C=1则表示允许从低特权级的程序转移到该段执行。R位表示该代码段是否可读。

A位表示该段最近是否被访问过,每当该段被访问时,CPU会自动将该位置置1。

AVL位是用户自定义位,通常由操作系统来使用。

为方便起见,我写了一个段描述符编辑器,为后续解析段描述符和快速构造段描述符提供一些帮助,如下图所示:
《x86汇编语言 从实模式到保护模式》学习实验笔记【10】进入保护模式
该工具的源代码工程下载链接如下所示:
https://download.csdn.net/download/BakerTheGreat/12839673

安装全局段描述符并加载GDTR

为简单起见,本示例程序我们只使用主引导程序。在主引导程序中,我们使用4个段:

  • 代码段
  • 数据段
  • 堆栈段
  • 显示内存段

同时,由于x86的CPU规定GDT中的第一个段描述符必须是空描述符,所以共有5个段。

代码段

由于我们只使用了主引导程序,所以显然代码段的长度是512个字节。所以段的粒度位G=0,即以字节为单位。段界限是0x1FF。描述符类型S=1,表示是代码段或数据段。32位,所以DB=1,L=0。该段目前位于内存中,所以P=1。段的特权等级是0,代码段只可执行,不能读写。
《x86汇编语言 从实模式到保护模式》学习实验笔记【10】进入保护模式
需要注意的一点是,x86的CPU都是小端序的,即低字节的数据在低地址,高字节的数据在高地址。
所以,该描述符的值为:

  • 低4字节: 0x7C0001FF
  • 高4字节: 0x00409800

数据段

为方便起见,我们把整个4GB内存都设置为可读写的数据段。为此,粒度位G=1,段界限为0xFFFFF,所以段长度为1M x 4K = 4GB。
段的线性基地址是0x00000000,属于存储器段S=1,DB=1,L=0,P=1,段特权级DPL=0
《x86汇编语言 从实模式到保护模式》学习实验笔记【10】进入保护模式

所以,该描述符的值为:

  • 低4字节: 0x0000FFFF
  • 高4字节:0x00CF9200

堆栈段

按照之前的介绍,我们将栈区设置为0x7C00~0x7A00,共512字节。
我们将esp的值初始化为0x7C00,将段的线性基地址设置为0x00000000,粒度为字节,特权级为0,TYPE=0b0110,即可读可写的、向下扩展的数据段。
《x86汇编语言 从实模式到保护模式》学习实验笔记【10】进入保护模式

所以,该描述符的值为:

  • 低4字节: 0x00007a00
  • 高4字节:0x00409600

显存段

和数据段很类似,不过段长度为64KB,段线性基地址是0x000B8000。
《x86汇编语言 从实模式到保护模式》学习实验笔记【10】进入保护模式
所以,该描述符的值为:

  • 低4字节: 0x8000FFFF
  • 高4字节:0x0040920B

相关文章: