汇编学习笔记(16) - 内存分段管理
总览图
说明
寄存器:
CR0,CR1,CR2,CR3 的作用如上图所示,其中需要注意的就是PE和PG两个位开关,他们分别指示了分段内存和分页内存的启用和禁用,同时PE也指示CPU工作的模式是实模式还是保护模式,所以实模式是和分段的内存管理捆绑的
GDTR: 记录了全局段表的位置和长度,是全局共用的,地址一般是不会改变的,最多1024个表项,占用8K
LDTR: 是一个指向全局段表的选择子,通过这个选择子可以在全局段表中定位一个段表项从而获取到局部段表的位置和长度,表项类型必须是LDT,他的目的应该是一个任务一个局部段表
TR: 同样LDTR一样,是也一个必须指向GDT的选择子,且指向的GDT段表项必须是TSS类型
IDTR: 和GDTR一样,只不过最多只能有256个表项,占2K, 只能有中断门,陷阱门,任务门
选择子:
如上图所示,选择子是16位的,前13位是表内索引,由于一个表项8字节,所以Index*8就是表内偏移,TI字段指示的是在GDT表中寻找还是在LDT表中寻找,RPL表示本次寻址要求的权限级别
80386保护模式下改变了8086 [段基址:偏移地址] 的寻址方式,变成了 [选择子:偏移地址] 的寻址方式, 所以段寄存器中存储的不再是段偏移地址,而是符合 选择子 格式的数据
具体的寻址过程如下:
假设如下寻址 [DS: AX]
- 首先取出DS中的选择子 根据 TI 的决定接下来是从GDT表中寻找段相关信息,还是从LDT表中寻找段相关信息
a. 如果是GDT则直接从GDTR寄存器中得到GDT的起始地址和表长度
b. 如果是LDT则需要使用LDTR寄存器中的选择子,从GDT表中找到对应记录LDT表信息的表项从而得到LDT的起始地址和表长度,寻找的过程就是重复本过程,注意LDTR中的选择子的TI必须是0 否则就无线嵌套了
- 决定了寻找用的表之后,使用选择子中的Index乘上8得到表内偏移地址(一个表项8字节),与表长度比较如果没异常,则根据表的起始位置加上表的偏移, 找到表项使用表项中的信息确定段基础地址 和 段界限
- 判断AX中的偏移地址是否超过了 段界限
- 直接使用段基地址+偏移地址的方式得到实际寻址的地址,注意这里的段基地址不用乘上16
以上过程是CPU自动完成的,不需要我们参与,只需要设置好对应的数据结构
注意: 为了加速, 所有使用选择子的地方(所有的段寄存器,LDTR, TR)都是用了不透明的缓存机制, 以上面的例子为例,DS寄存器在寻址过一次之后,它的缓存会直接记住 实际的段基地址和段界限等等,如果再次使用DS寄存器寻址
则可以直接跳过1,2两步,直接从第三部开始, 同理LDTR也是他的缓存直接记录了 LDT的位置和大小等
缓存的刷新机制: 缓存的刷新是在写入值的时候进行的, 所以如果实际表项发生了变化,段寄存器的缓存是不会跟着变的,这样就可能指向了错误的地方,所有要手动设置一下相关寄存器
段表/段表项:
一共有三种段表 GDT、 LDT 、IDT,
GDT和IDT应该是只有一张的,而LDT则是多张的,他们拥有相同的结构
每个表占用8字节,其结构定义如上图,一共分为2大类,结构上略有差异,通过共有属性DT来区分
1. 段描述符 DT = 1
一个段的描述符,分为两个小类,通过属性E 来区分
a. 数据段描述符 E = 0
b. 代码段描述符 E = 1
在这种情况下,通过标志位D来区分是16位代码段还是32位代码段
D = 1 : 32位代码段
D = 0 : 16位代码段
他们各自的一些属性请看上图
2. 门或系统描述符 DT =0
系统段或者门的描述符,分为5个小类,通过属性TYPE确定
门描述符都不直接指向某个段,而是通过选择子指向了GDT中的表项从而间接的指向某个段。
a. 系统段 TYPE = 1, 2, 3, 9, B
基本上指向的都是一些数据结构,不具有实际的代码执行能力的数据
系统段描述符的结构是采用段描述符的而不是门描述符的,只不TYPE字段不像存储段那样分开解释了
b. 任务门 TYPE = 5
任务门存储了一个选择子,这个选择子指向了一个 系统段,而这个系统段必须指向一个 TSS 数据,这样当发生任务门调用的时候,就能找到对应的TSS结构发生任务切换了
通过CALL或JMP 指令调用,任务门指向GDT中的TSS段,偏移地址无效,使用TSS中的信息
c. 调用门 TYPE = 4, C
相当于记录了一个函数的地址
通过CALL 指令调用,调用门指向GDT中的代码段,指令的偏移地址就会段内偏移地址
d. 中断门 TYPE = 6, E
相当于记录了一个函数的地址,与任务门的区别就是调用方式的区别, 只能存放在IDT中
通过中断进入,中断门指向代码段,门中偏移地址就是段内偏移地址
e. 陷阱门 TYPE = 7, F
相当于记录了一个函数的地址,与中断门的唯一区别是,中断门默认自动关中断,而陷阱门不自动关中断, 只能存放在IDT中
通过中断进入,陷阱门指向代码段,门中偏移地址就是段内偏移地址
总结:
- 新增了4种地址寄存器和3种表,其中GDTR和其所指向的GDT是最重要的,因为其他的寄存器和表都需要通过GDT来完成工作
- 根据段表项结构的解释我将其分为两大类,段类型 和 门类型,段类型都直接指向一个数据段,它需要配合偏移地址来使用, 他包括段描述符和系统描述符。门类型 包含一个指向段类型的选择子和一个偏移地址来指向数据,门类型有点像是快捷方式。
- 段基址现在不直接给出,而是通过选择子在GDT或者LDT表中寻找属性是 段类型 的段表项,然后根据实际的表项类型确定选择子指向的真实地址
注意: 以上步骤是不完整的,其中隐藏掉了所有权限判断的细节。
TSS
CPU远程支持了线程的概念,称之为任务,而TSS就在任务切换的时候保存各寄存器环境的数据块
其结构如下:
连接字段:
当任务别切换的时候用于指向前一个任务的选择子,不一定有
IO许可位图:
IO位图也是存储在本段中,他位于TTS结构之后,TSS中的IO许可位图字段表示许可位图在本段中的偏移地址,因为IO位图最大8K字节,所以偏移位置不能大于56K 而IO位图的每一位表示 IO端口 不受IOPL限制随意访问, 比如 IO位图的地M位决定,IN AL, M,位图位0 表示可以随意访问,1 表示是受IOPL控制
ESP0-2与SS0-2:
每个特权级都有自己的堆栈,当特权从外往内时,根据特权级选中对应的数据初始化堆栈,并将外层的指针压栈。
当特权从外往内时是,根据特权级选中对应的数据初始化堆栈,并将外层的指针压栈。
当从内层返回外层的时候用堆栈中保存的数据恢复堆栈,但是内层的SS数据是不回写TSS,这意味着,一旦从内层返回到外层,内层的堆栈就清空了。
CR3和LDT:
也就说每个任务都有自己的 局部表和页表
T:
占一位,如果T=1 表示当切换到此任务后直线第一条指令前,先触发一个调试中断