x86 CPU的保护模式——全局描述符表(二)


查看系列文章点这里: 操作系统真象还原

前言

  在前一篇文章我们主要讲了一下保护模式和实模式之间的主要变化,在这篇文章中,我们主要讲一下保护模式下的寻址过程,也就是如何得到真实的物理地址。


一、段描述符

  顾名思义,段描述符就是用来描述一个段的相关信息的结构,它的大小是8字节,并且位于内存之中。直接来看一下它长啥样吧,见下表:

位置63~565554535251~484746~454443~4039~1615~0
说明段基址31~24GD/BLAVL段界限19~16PDPLSTYPE段基址23~0段界限15~0
  • 段基址(32位):被分割成2个部分,在寻址时才会被拼凑到一起;
  • 段界限(20位):段边界的扩展最值,从0开始计算,用来计算段的长度范围。有两个单位,分别是1字节和4KB,因此一个段的长度=段界限*单位;
  • S、TYPE:两者一起共同决定段的类型;
  • DPL:表示该段描述符表示的段的特权级,共有0、1、2、3四个等级,数字越小特权越高。
  • P:段是否在内存中,P=1则在,P=0则不在;
  • AVL:对用户来说是否可用,操作系统和硬件不受此为控制;
  • L:是否是64位代码段;
  • D/B:用来指定段内偏移地址和操作数大小,对代码段来说,此位是D,D=0表示16位,使用IP寄存器,D=1表示32位,使用EIP寄存器;对栈段来说,此位是B,B=0表示16位,使用SP寄存器,B=1表示32位,使用ESP寄存器;
  • G:表示粒度,就是段界限的单位,G=0表示单位是1字节,G=1表示单位是4KB;

  上面把段描述符的每一位的作用都简单的说了一下,有些是现在依然很常用的,有些则是历史遗留产物,在实际中用处不大。不过还有S、TYPE这两个字段没有解释清楚,因为这两个字段比较复杂,单独拿出来讲一下。

  S位用来指示该段是系统段(S=0)还是非系统段(S=1),只有先确定了S,TYPE字段才有具体的意义。

  在讲TYPE字段之前,补充一些关于段的分类,避免搞混了。
  对于CPU来说,它将段分成两大类,凡是硬件运行需要的段就是系统段,凡是软件运行需要的段就是非系统段(包括操作系统)。
  咱们现在所讲的这个段描述符的结构,其实是用来描述非系统段的。系统段有它们自己的描述符,只不过名字不一样,通常也是“XXX描述符”。
  但是它们都有一个共同点,都是8字节,在44、43~40位置都是S和TYPE字段,这样便于CPU区分它们。

  接下来我们来详细说说TYPE字段如何将系统段或者非系统段再次划分,具体见下表:

系统段 系统段类型 第3~0位 说明
3 2 0 1
未定义 0 0 0 0 保留
可用的 80286 TSS 0 0 0 1 仅限286的任务状态段
LDT 0 0 1 0 局部描述符表
忙碌的 80286 TSS 0 0 1 1 仅限286
80286 调用门 0 1 0 0 仅限286
任务门 0 1 0 1 现代操作系统很少用到
80286 中断门 0 1 1 0 仅限286
80286 陷阱门 0 1 1 1 仅限286
未定义 1 0 0 0 保留
可用的 80386 TSS 1 0 0 1 386以上CPU
未定义 1 0 1 0 保留
忙碌的 80386 TSS 1 0 1 1 386以上CPU
80386 调用门 1 1 0 0 386以上CPU
未定义 1 1 0 1 保留
中断门 1 1 1 0 386以上CPU
陷阱门 1 1 1 1 386以上CPU
对于非系统段,按代码段和数据段划分,分别有不同的意义
非系统段 内存段类型 X C R A 说明
代码段 1 0 0 * 只执行代码段
1 0 1 * 可执行、可读代码段
1 1 0 * 可执行、一致性代码段
1 1 1 * 可执行、可读、一致性代码段
内存段类型 X C R A 说明
数据段 0 0 0 * 只读数据段
0 0 1 * 可读写数据段
0 1 0 * 只读、向下扩展数据段
0 1 1 * 可读写、向下扩展数据段

  下面在解释一下非系统段各字段的含义:

  • A:Accessed位,由CPU自动设置,我们只需要查看,每当CPU访问过后,就将此位置1,在创建新的段描述符时置0;
  • C:是否是一致性代码段,C=1表示是;
  • R:是否可读,R=1表示可读;
  • X:是否可执行,X=1表示可执行;
  • E:标识段的扩展方向,E=0表示向上扩展,E=1表示向下扩展;
  • W:是否可写,W=1表示可写;

二、全局描述符表

  在了解了段描述符,那么 全局描述符表(GDT) 就很好理解了,就是用来存储段描述符的一个数据结构,可以理解为数组。它的位置也在内存中,通过 GDTR 寄存器就可以在内存中找到它,通过 lgdt 指令可以为该寄存器赋值,该寄存器是一个48位的寄存器,如下表:

47~1615~0
GDT内存起始地址GDT界限

  起始地址就是一个段基址,因为 GDT 本身也相当于一个特殊的数据段,想要访问,也必须要有段基址。GDT 界限是段边界扩展最值,就是用来定义这个表的大小的一个值,16位最大值为65536,表示这个表最大为65536字节,一个段描述符8字节,所以一个 GDT 最多可容纳 65536 / 8 = 8192 个段描述符。

三、选择子

  GDTR 寄存器给出了 GDT 的段基址,就还差一个偏移地址就可以找到一个段描述符了,这个偏移地址就存储在段寄存器当中,因为存放的以及不是段基址了,所以就新取了一个名字——选择子,结构如下表所示:

15 ~ 421 ~ 0
描述符索引值TIRPL
  • 描述符索引值:就是偏移地址或者说数组下标,一共13位,最多可索引 8192 个段描述符;
  • TI:Table Indicator,用来指示段描述符在 GDT 中还是在 LDT 中;
  • RPL:请求特权级,分为0、1、2、3四种特权等级;

  值得注意的是,描述符索引值必须大于等于1,第0个是不能使用的,这是为了避免因忘记初始化选择子而造成不可预知的错误。

LDT(局部描述符表):
  有全局就肯定有局部,不过现代操作系统很少使用LDT,所谓局部就是指专门为某一个任务创建的描述符表,该表容纳的段只有该任务会使用。
  LDT同样也在内存之中,LDTR 负责指向它,lldt 指令用于为该寄存器赋值。而且 LDT 是一个系统段,也就是说它也需要先在 GDT 中注册,然后用选择子去访问它。并且 CPU 每切换一个任务,就要重新加载 LDT。


总结

  本文在好几个地方都提到了特权级或者系统段,这一部分知识和 GDT 关系不大,而且一两句话说不清楚,等到后面专门在讲解啦!

  持续更新中~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值