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]
a. 如果是GDT则直接从GDTR寄存器中得到GDT的起始地址和表长度
b. 如果是LDT则需要使用LDTR寄存器中的选择子,从GDT表中找到对应记录LDT表信息的表项从而得到LDT的起始地址和表长度,寻找的过程就是重复本过程,注意LDTR中的选择子的TI必须是0 否则就无线嵌套了
以上过程是CPU自动完成的,不需要我们参与,只需要设置好对应的数据结构
注意: 为了加速, 所有使用选择子的地方(所有的段寄存器,LDTR, TR)都是用了不透明的缓存机制, 以上面的例子为例,DS寄存器在寻址过一次之后,它的缓存会直接记住 实际的段基地址和段界限等等,如果再次使用DS寄存器寻址
则可以直接跳过1,2两步,直接从第三部开始, 同理LDTR也是他的缓存直接记录了 LDT的位置和大小等
缓存的刷新机制: 缓存的刷新是在写入值的时候进行的, 所以如果实际表项发生了变化,段寄存器的缓存是不会跟着变的,这样就可能指向了错误的地方,所有要手动设置一下相关寄存器
一共有三种段表 GDT、 LDT 、IDT,
GDT和IDT应该是只有一张的,而LDT则是多张的,他们拥有相同的结构
每个表占用8字节,其结构定义如上图,一共分为2大类,结构上略有差异,通过共有属性DT来区分
一个段的描述符,分为两个小类,通过属性E 来区分
他们各自的一些属性请看上图
系统段或者门的描述符,分为5个小类,通过属性TYPE确定
门描述符都不直接指向某个段,而是通过选择子指向了GDT中的表项从而间接的指向某个段。
基本上指向的都是一些数据结构,不具有实际的代码执行能力的数据
系统段描述符的结构是采用段描述符的而不是门描述符的,只不TYPE字段不像存储段那样分开解释了
任务门存储了一个选择子,这个选择子指向了一个 系统段,而这个系统段必须指向一个 TSS 数据,这样当发生任务门调用的时候,就能找到对应的TSS结构发生任务切换了
通过CALL或JMP 指令调用,任务门指向GDT中的TSS段,偏移地址无效,使用TSS中的信息
相当于记录了一个函数的地址
通过CALL 指令调用,调用门指向GDT中的代码段,指令的偏移地址就会段内偏移地址
相当于记录了一个函数的地址,与任务门的区别就是调用方式的区别, 只能存放在IDT中
通过中断进入,中断门指向代码段,门中偏移地址就是段内偏移地址
相当于记录了一个函数的地址,与中断门的唯一区别是,中断门默认自动关中断,而陷阱门不自动关中断, 只能存放在IDT中
通过中断进入,陷阱门指向代码段,门中偏移地址就是段内偏移地址
总结:
注意: 以上步骤是不完整的,其中隐藏掉了所有权限判断的细节。
原文:https://www.cnblogs.com/alwaysking/p/12339641.html