全局描述符表

在开始介绍全局描述符之前,先了解一下段描述符。

段描述符

内存段是一片内存区域,访问内存就要提供段基址(段基址属性)以及段界限属性(约束段大小)。

段描述符是位于GDT( Global Descriptor Table, GDT )或LDT( Local Descriptor Table, GDT )中8字节大小的表项,用来描述内存段的属性。给处理器提供一个段的位置、界限、访问特权级等信息。具体格式如下图所示。

在这里插入图片描述

段描述符格式(人为划分高低位)

保护模式下地址总线宽度是 32 位,段基址需要用 32 位地址来表示。

段描述符属性介绍

  • 低32位 :第 0~15 位用来存储段的段界限前 0~15 位。段界限表示段边界的扩展最值,即最大扩展到多少或最小扩展到多少。
    实际段界限 = ( 描述符中段界限 + 1 ) ∗ 粒度 − 1 实际段界限=(描述符中段界限+1)∗粒度−1 实际段界限=(描述符中段界限+1)粒度1其中,G表示段界限粒度,0表示粒度为1byte,1表示粒度为4KB。
  • 低32位 :第 16~31 位用来存储段基址 0~15 位。
  • 高32位 :第 0~7 位是段基址的 16~23 位。
  • 高32位 :第 8~11 位是 type 字段,共 4 位,用来指定本描述符的类型。type 字段和 S 字段配合使用。
  • 高32位 :第 12 位是 S 字段。S为 0 时表示系统段,S 为 1 时表示数据段。【解释】凡是硬件运行要用到的东西都可称之为系统,凡是软件(代码,数据,栈)需要的东西都称为数据。
  • 高32位 :第 13~14 位是 DPL(Descriptor Privilege Level) 字段,即描述符特权级。2位能表示 4 种特权级,数字越小,特权级越大。
  • 高32位 : 第 15 位是 P (Present) 字段,即段是否存在。如果段存在于内存中,P 为 1,否则 P 为 0。
  • 高32位 : 第 16~19 位是段界限的第 16~19 位。
  • 高32位 :第 20 位为 AVL 字段,
  • 高32位 :第 21 位为 L 字段,用来设置是否是 64 位代码段。L 为 1 表示 64 位代码段,否则表示 32位代码段。
  • 高32位 :第 22 位是 D/B 字段,用来指示有效地址(段内偏移地址)及操作数的大小。
  • 高32位 :第 23 位是 G 字段,Granularity,粒度,用来指定段界限的单位大小。
  • 高32位 :第 24~31 位是段基址的第 24~31 位。

全局描述符表GDT

全局描述符表( Global Descriptor Table, GDT )是保护模式下内存段的登记表全局描述符表 GDT 相当于是描述符的数组,数组中的每个元素都是 8 字节的描述符。可以用选择子(马上会讲到)中提供的下标在 GDT 中索引描述符。

段描述符与内存段的关系图
在这里插入图片描述

全局描述符表存放在内存中,需要用专门的寄存器(GDTR,GDT Register)指向它(GDT )后,CPU 才知道它的位置。

【补充】

GDTR 是个 48 位的寄存器,专门用来存储 GDT 的内存地址及大小。

GDTR 中48 位内存数据划分为两部分,其中前 16 位是 GDT 以字节为单位的界限值,所以这 16 位相当于GDT 的字节大小减 1。后 32 位是 GDT 的起始地址。由于 GDT 的大小是 16 位二进制,其表示的范围是 2的16次方等于65536字节。每个描述符大小是8字节,故,GDT中最多可容纳的描述符数量是65536/8=8192个,即 GDT 中可容纳 8192 个段或门。

在这里插入图片描述

段选择子

选择子(16位)的作用主要是确定段描述符,达到确定描述符的目的。

在这里插入图片描述
参数解释

  • RPL:请求特权级别
  • TI(Table Indicator)
    ●TI=0:查GDT表
    ●TI=1:查LDT表
  • INDEX(描述符索引值):此值用来在 GDT 中索引描述符

✳保护模式下访问内存地址实例

前提:选择子为 0x8,将其加载到 ds 寄存器后,访问 ds:0x9 的内存地址。

过程:分析选择子 0x8 ,其中 低 2 位是RPL,其值为 00。第 3 位是 TI,其值 0,表示是在 GDT 中索引段描述符。高 13 位 0x1 在 GDT 中的第 1 个段描述符(GDT 中第 0 个段描述符不可用)。这样,用 第 1 个段描述符中的 3 个段基址部分与段内偏移地址 0x9 相加所得的和 X 作为访存地址

进入保护模式步骤

准备进入保护模式的3个步骤:

  1. 打开A20
  2. 加载gdt
  3. 将cr0的pe位置1

【补充1】 打开A20地址线

打开 A20Gate只需要将端口 0x92 的第 1 位置 1即可

in al,0x92
or al,0000_0010B
out 0x92,al

【补充2】 CR0 寄存器的 PE 位

PE 为 0 表示在实模式下运行,PE 为 1 表示在保护模式下运行。

mov eax, cr0   ;将 cr0 写入 eax。
or eax, 0x00000001 ;通过或运算 or 指令将 eax 的第 0 位置 1。
mov cr0, eax  ;将 eax 写回 cr0,这样 cr0 的 PE 位便置为 1

参考资料

  • 《操作系统真象还原》
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,需要了解一下全局描述符(Global Descriptor Table,GDT)是什么。GDT是在操作系统启动时由CPU初始化的一个数据结构,用于管理内存段的访问权限。操作系统可以通过修改GDT来控制内存的访问权限和保护。 在Linux系统中,GDT的初始化是在setup.s文件中完成的。下面是setup.s文件中初始化GDT的代码和注释: ``` /* * 初始化GDT */ init_gdt: lgdt gdtr /* 加载GDT寄存器 */ /* GDT项0:空描述符 */ gdt_null: .word 0, 0 .byte 0, 0, 0, 0 /* GDT项1:内核代码段描述符 */ gdt_code: .word 0xffff, 0 /* 段界限 */ .byte 0, 0, 0x9a, 0xcf /* 基地址为0,代码段可读可执行,DPL为0,系统段,粒度为4KB */ /* GDT项2:内核数据段描述符 */ gdt_data: .word 0xffff, 0 /* 段界限 */ .byte 0, 0, 0x92, 0xcf /* 基地址为0,数据段可读可写,DPL为0,系统段,粒度为4KB */ /* GDT项3:用户代码段描述符 */ gdt_user_code: .word 0xffff, 0 /* 段界限 */ .byte 0, 0, 0x9a, 0xcf /* 基地址为0,代码段可读可执行,DPL为3,用户段,粒度为4KB */ /* GDT项4:用户数据段描述符 */ gdt_user_data: .word 0xffff, 0 /* 段界限 */ .byte 0, 0, 0x92, 0xcf /* 基地址为0,数据段可读可写,DPL为3,用户段,粒度为4KB */ /* GDT项5:TSS描述符 */ gdt_tss: .word 0x67, 0 /* 段界限 */ .byte 0, 0, 0x89, 0x40 /* 基地址为tss_entry,DPL为0,系统段,粒度为1B */ .word 0 /* tss_entry的低16位 */ .byte 0 /* tss_entry的第24位 */ .byte 0x00 /* tss_entry的第32位 */ .word 0 /* tss_entry的高16位 */ /* GDT项6:LDT描述符 */ gdt_ldt: .word 0, 0 /* 段界限 */ .byte 0, 0, 0x82, 0x40 /* 基地址为ldt,DPL为0,系统段,粒度为1B */ /* GDT项7:TSS段 */ gdt_tss_entry: .word 0, 0 /* 填充 */ .word 0x10, 0 /* ss0 */ .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 填充 */ /* GDT项8:LDT段 */ gdt_ldt_entry: .word 0, 0 /* 填充 */ .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 填充 */ /* GDT项9:TSS段 */ gdt_tss2_entry: .word 0, 0 /* 填充 */ .word 0x10, 0 /* ss0 */ .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 填充 */ /* GDT项10:LDT段 */ gdt_ldt2_entry: .word 0, 0 /* 填充 */ .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 填充 */ /* GDT项11:TSS段 */ gdt_tss3_entry: .word 0, 0 /* 填充 */ .word 0x10, 0 /* ss0 */ .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 填充 */ /* GDT项12:LDT段 */ gdt_ldt3_entry: .word 0, 0 /* 填充 */ .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 填充 */ .fill 16 * 8 - (. - init_gdt) /* 填充到16字节 */ /* GDT */ gdt: .word 16 * 8 - 1 /* GDT长度 */ .long gdt /* GDT地址 */ /* GDT寄存器 */ gdtr: .word 16 * 8 - 1 /* GDT长度 */ .long gdt /* GDT地址 */ ``` 上述代码中,首先使用`lgdt`指令将GDT的长度和地址加载到GDT寄存器中。然后,定义了一些GDT项,包括空描述符、内核代码段描述符、内核数据段描述符、用户代码段描述符、用户数据段描述符、TSS描述符和LDT描述符。其中,每个GDT项包括段界限、基地址、段属性等信息。 最后,定义了一个GDT数组`gdt`,将GDT长度和地址存储在`gdtr`寄存器中,完成了GDT的初始化。 需要注意的是,上述代码只是一个简化的示例,实际的GDT项可能更加复杂,具体的实现方式也可能因操作系统的不同而有所差异。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值