来源:https://blog.csdn.net/qq_37653144/article/details/82821540
https://blog.csdn.net/yeruby/article/details/39718119
https://blog.csdn.net/lindorx/article/details/89410113
全局描述表(GDT Global Descriptor Table):在保护模式下一个重要的数据结构。
GDT可以被放在内存的任何位置,那么当程序员通过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,所以Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。
GDT是保护模式所必须的数据结构,也是唯一的–不应该,也不可能有多个。另外,正象它的名字(Global Descriptor Table)所揭示的,它是全局可见的,对任何一个任务而言都是这样。
除了GDT之外,IA-32还允许程序员构建与GDT类似的数据结构,它们被称作LDT(Local Descriptor Table),但与GDT不同的是,LDT在系统中可以存在多个,并且从LDT的名字可以得知,LDT不是全局可见的,它们只对引用它们的任务可见,每个任务最多可以拥有一个LDT。另外,每一个LDT自身作为一个段存在,它们的段描述符被放在GDT中。
gdt表在x86架构中用来存储内存的分段信息,通过段选择子进行访问,表的大小=0x10000=65536字节,每个表项占8字节,第一个表项为空,不使用,因此一共有8191个可用表项。表项结构如下
(图片来自https://blog.csdn.net/yeruby/article/details/39718119)
段界限表示段边界的扩展极值,即最大扩展到多少或最小扩展到多少。扩展方向只有上下两种,对于数据段和代码段,段的扩展方向是向上,即从低地址向高地址扩展,此时的段界限用来表示段内偏移的最大值(上界);对于栈段,段的扩展方向是向下,即从高地址向低地址扩展,此时的段界限表示段内偏移的最小值(下界)。无论是向上还是向下,段界限都表示段的边界。段界限字段给出的只是数值,其单位(或称粒度)则在G位中给出,G位为0则粒度为B,为1则为4KB。因此段界限边界值的计算公式为:
(段界限字段值+1)*(粒度大小)- 1
内存访问需要用到“段基址:段内偏移地址”,段界限其实是用来限制段内偏移地址的,段内偏移地址必须位于段描述符给出的范围之内,否则CPU会抛出异常。任何超范围的偏移地址都被认为是非法的,CPU会捕获这个异常。
段基地址为该段的首地址,占用32bit;type说明了段的属性,网络资料很多,不再详述,占用4bit;
DPL为段的权限,占用2bit,只有访问程序的权限高于等于该段的权限才能使用这个段,权限分为4级,从高到低为0、1、2、3;
P=1说明该段可以使用,否则将当作该段不存在,如果使用段选择子强行访问会发生段异常,中断号为11;
AVL由软件设定,cpu不管这个位;
D/B有两种:当这个段被type设置为代码段时,D=1代表这是32位的程序,D=0代表这是16位的程序;如果是向下拓展的数据段,则称为B位,B=1代表最大可访问范围是4GB,B=0代表最大可访问范围是64k,如果表示的是堆栈段,则B=1,使用esp寄存器作为栈顶寄存器,否则使用sp寄存器
属性字段中的type字段用来指定段描述符的类型,而S位的数值决定了type字段中不同位的含义。一个段描述符首先分为两大类,要么是系统段(S位置0),要么是非系统段(S位置1),或称数据段。对于CPU而言,凡是硬件运行需要用到的东西都可称之为系统(如硬件在内存中的映射),凡是软件需要用到的东西(操作系统也是软件,对CPU而言在这一层面它与用户程序无区别)都是数据。无视是代码还是数据,都是作为硬件的输入,因此我们常说的代码段在段描述符中也属于数据段(非系统段)。type字段要和S字段配合才能确定段描述符的确切类型,只有S字段的值确定后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,用来表示段是否可写。
非系统段,另一种更清晰的描述:
段选择子是什么?
引用GDT和LDT中的段描述符所描述的段,是通过一个16-bit的数据结构来实现的,这个数据结构叫做Segment Selector——段选择子。它的高13位作为被引用的段描述符在GDT/LDT中的下标索引,bit 2用来指定被引用段描述符被放在GDT中还是到LDT中,bit 0和bit 1是RPL——请求特权等级,被用来做保护目的。如图所示:
前面所讨论的装入段寄存器中作为GDT/LDT索引的就是Segment Selector,当需要引用一个内存地址时,使用的仍然是Segment:Offset模式,具体操作是:在相应的段寄存器装入Segment Selector,按照这个Segment Selector可以到GDT或LDT中找到相应的Segment Descriptor,这个Segment Descriptor中记录了此段的Base Address,然后加上Offset,就得到了最后的内存地址。
段选择子包括三部分:描述符索引(index)、TI、请求特权级(RPL)。它的index(描述符索引)部分表示所需要的段的描述符在描述符表的位置,由这个位置再根据在GDTR中存储的描述符表基址就可以找到相应的描述符。然后用描述符表中的段基址加上逻辑地址(SEL:OFFSET)的OFFSET就可以转换成线性地址,段选择子中的TI值只有一位0或1,0代表选择子是在GDT选择,1代表选择子是在LDT选择。请求特权级(RPL)则代表选择子的特权级,共有4个特权级(0级、1级、2级、3级)。
关于特权级的说明:任务中的每一个段都有一个特定的级别。每当一个程序试图访问某一个段时,就将该程序所拥有的特权级与要访问的特权级进行比较,以决定能否访问该段。系统约定,CPU只能访问同一特权级或级别较低特权级的段。
例如给出逻辑地址:21h:12345678h 转换为线性地址的步骤如下:
(1)、选择子SEL=21h=0000000000100 0 01b 它代表的意思是:选择子的index=4即选择GDT中的第4个描述符;TI=0代表选择子是在GDT选择;最后的01代表特权级RPL=1
(2)、OFFSET=12345678h若此时GDT第四个描述符中描述的段基址(Base)为11111111h,则线性地址=11111111h+12345678h=23456789h
段寄存器实际是 96 位
选择子 16 位
属性 16 位
基地址 32 位
界限 32 位。
其中属性实际有效的是 12位,如上所属。而这 16 位刚好来自 上面 GDT 表中的那 16 位(其中限长 9…16 在这里忽略)
界限 32 位。如上面所说是 20位。实际根据G位确定(G位为0则粒度为B,为1则为4KB。)
20位最大值就是 0XFFFFFF 。
如果G 为0 则是 0X00FFFFFFFF
如果G 为1 则是 0XFFFFFFFFFF