保护模式下的地址变换

1. 简介

在保护模式中,操作系统的段寄存器只是一个索引,用于在GDT/LDT表中获得一个32位的基地址。本文对转换过程及涉及到的概念如GDTR、GDT、LDT等做介绍。

2. GDTR

Global Descriptor Table Register,全局描述符寄存器,长度48位,低16位是GDT长度限制,高32位是GDT在内存中的地址,如下图所示;

此外,段基地址寄存器还包括还有IDTR、LDTR、TR。

IDTR,中断描述符表寄存器:与GDTR的作用类似,IDTR 寄存器用于存放中断描述符表 IDT的 32 位线性基地址和 16 位表长度值。

LDTR:存放LDT的32位线性基地址、16位段限长、描述符属性值

TR:存放了当前任务TSS段的16位选择符、32位基地址、16位段长、描述符属性值。

image

3. GDT

即全局描述表。段描述符表是段描述符的一个数组,共有2中描述符表:全局描述符表(GDT Global Descriptor Table)与局部描述符表LDT(Local descriptor table)。

GDT表一般由操作系统分配,并可用汇编指令lgdt把表的地址和长度装载入GDTR中。表中包含最多8192个段描述符(因为段选择子的索引共13位)。

GDT表用于在保护模式下的逻辑地址转换到线性地址。

4. 为什么要用GDT表?

这里说下自己的理解,之所以要用一个16位段选择符去GDT/LDT表查找出段的基地址,因为:

1. 保持与16位系统兼容;当使用16位时,段选择子就是一个段寄存器,左移4位加上偏移量即得到线性地址;而在保护模式下,仍然使用16位的段选择子可以去GDT/LDT表中索引获得32位的地址。

2.检查权限。GDT表中DPL描述了权限级别。

5. LDT

(Local Descriptor Table)局部描述符表可以有若干张,每个任务可以有一张。我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。如图

图:http://www.techbulo.com/708.html

6. 段描述符

段描述符是GDT/LDT表中的一个数据结构项,包含了一个段的位置、大小、特权级别等信息。每个段描述符占用8字节(64位),包含了段基地址、段限长、段属性。

  1. 段基地址(Base address),指定段在线性地址空间中的开始地址。基地址是线性地址,对应于段中偏移0处。

  2. 段限长(limit),是虚拟地址空间中段内最大可用偏移位置。它定义了段的长度。
    段内到limit的地址范围对应线性地址中base~base+limit的范围,当偏移量大于limit则会异常。

  3. DPL:描述符特权级。硬件会比较CPL(当前的特权级)和DPL(目标段的特权级)进行特权级的检查。

7. 段选择子

GDTR中存储了GDT表的基地址,那么我们要怎么访问GDT表中的某一项段描述符呢?答案是用段选择子去索引。段选择子是一个16位的寄存器(在实模式下它就是一个段寄存器,而保护模式下,他就是一个索引,用来去GDT/LDT表中获取段描述符)。

段选择子包括3个字段:

  1. 请求特权级RPL(Requested Privilege Level)。RPL提供了段保护信息,用于鉴权。
  2. 表指示标志TI (Table Index)。TI为0表示描述符在GDT表中,为1表示描述符在LDT表中。
  3. 描述符索引值(Index)。用于在GDT/LDT表中索引。

8. 保护模式下如何把逻辑地址转为物理地址

保护模式下地址是一定分段的,所以逻辑地址需要先转为线性地址;若开启了分页机制,则还要进行页表查找,得到内存块号,与偏移量做运算即得到物理地址。

8.1 逻辑地址转为物理地址

为了把逻辑地址转换成一个线性地址,处理器会执行以下操作;

  1. 使用段选择子获取段描述符。以GDT表为例,GDTR存储了GDT表的基地址,我们以段选择子作为索引,计算出偏移量后即可在 GDT表中定位,得到相应的段描述符。(仅当一个新的段选择子加载到段寄存器中时才需要这一步。)
  2. 段描述符中包含了描述符特权级DPL与段限长limit。当前执行程序的特权级CPL(存储在处理器寄存器的2个比特位中)会与DPL做比较,处理器在检测到违反特权级的操作时,会产生一个保护性异常。具体的比较过程可以看这里。
    另外,cpu还会进行段限长limit的检查,防止程序寻址到段外内存。
  3. 把段描述符中取得的段基地址与偏移量相加,得到线性地址。

值得注意的是,段描述符中的段基地址是32位的,偏移量也是32位的;所以理论上一个段的最大空间是4G.

逻辑地址到线性地址

8.2 线性地址如何确定物理地址

保护模式下只有段式和段页式。因此,从线性地址得到物理地址有2种方式。

段式存储:在没有开启分页机制的情况下,由GDT/LDT表与偏移量得到的线性地址即为内存的物理地址了;

段页式存储:当开启分页机制后,需要将段式存储得到的线性地址利用分页机制转换位物理地址,分页机制的地址转换过程见下文。为了节省页表占用空间,采用多级页表的方式:31~22为一级页号,21~12 为二级页号,11~0为偏移量

实际在linux中,Linux则对所有的进程都使用了相同的段来对指令和数据寻址。即用户数据段;段的基地址都是0,也就是0+段内偏移,则偏移量与线性地址的值是相同的,参见这里。

8.3 分页机制下确定物理地址过程

以页面大小4KB(12位*1B),页表项大小(即页表每一项的大小,注意与页面大小区别)4B为例,一级页表只能为一页,则4KB/4B=2^10,所以一级页号占有10位;那么二级页号为32-10-12=10位;

一个32位的线性地址
  1. 通过一级页号(最高10位)为索引,在一级页表中查找到一个32位的数值,该数值为二级页表在内存中的起始物理地址;
  2. 这时继续以二级页号为索引,查找到对应的块号(即物理地址的高20位),则块号<<12 再加上偏移量即为物理地址
二级页表的查找过程

9. 计算机如何进入保护模式

李治军老师的操作系统课程

16位机左移4位加ip只有20位,1M的内存大小,显然不够;图中的代码jmpi 0,8也不是移到80地址处。我们需要切换到32位的寻址方式,使cpu走另外一条解释执行指令的电路。

关键代码如下:

mov ax, #0x0001
mov cr0,ax

这里赋值1到cr0,使得PE=1,进入保护模式;jmpi此时就是利用GDT表来寻址了,而不是跳到 8<<4 +0 = 80的地址。所以此时找的就是gdt表的8字节开始,偏移位0的地方了。

另外,当设置CR0的PG标志,即位31时,可以打开分页机制。


参考

《linux0.11完全注释》

http://www.voidcn.com/article/p-ngtadyko-bpw.html

http://www.techbulo.com/708.html

你可能感兴趣的:(保护模式下的地址变换)