80386通过两种转换把逻辑地址(应用程序所见的地址)转换成物理地址:
1、段转换:把逻辑地址(包括段地址和有效地址)转换成线性地址。
2、页转换:是把线性地址转换成物理地址,这个转换时可选的。
这些转换对应用程序时不可见的,图1从宏观上面描述了这两种转换:
图1 地址转换概况
1、段转换
图2详细描述了段转换的情况,在这个转换过程中,CPU用到如下的数据结构:
描述符(Descriptor)
描述符表(Descriptor tables)
选择子(Selector)
段寄存器(Segment registers)
图2 段转换
1)段寄存器和选择子
段寄存器就是指的是CS,DS,SS,ES,FS,GS。在实模式下面段寄存器存放着16位段地址,该段地址加上偏移地址就是物理地址。在虚模式下面,段地址需要查表来得到,段地址存放在描述符表(Descriptor table)中。段寄存器里面存放的是16位选择子(Selector),该选择子是描述附表的索引值,通过选择子就可以找到段地址,然后段地址加上32位的偏移地址就是线性地址(注意不是物理地址,有可能需要页转换)。
选择子的格式如下:
图3 选择子的格式
a) 表类型指示(TI):位于选择子的第2位。指明该选择子指示的是全局描述表(GDT)还是本地描述表(LDT),设0表示GDT,设1表示LDT。GDT和LDT在描述符表中介绍。
b) 请求权限级别(RPL):位于选择子的0,1位,表明段访问的优先级,用于CPU的保护机制。
c) 索引值(Index):位于选择子的高13位,表明需要访问的段在描述附表中的位置。
在虚拟模式下,段寄存器存放选择子。可以用显示的Mov指令来赋值,也可以用JMP或者Call来给CS赋值。另外如果每次CPU都取得索引值去描述附表中去查询段地址(描述附表存放在内存中)效率会非常低下,为了提高效率。CPU设计了段高速缓存,来降低对内存的访问次数。每个段寄存器分为可见部分和不可见部分,可见部分为16位,程序员可见。不可见部分用来存放描述符内容(段描述信息),该部分对程序员不可见,只对CPU可见。结构如下:
图4 段寄存器
当对可见部分赋值的时候,CPU后台用描述符表中对应的信息初始化不可见部分。以后CPU就通过访问不见部分来访问段,这样可以大大降低内存访问的次数。
2)描述符和描述附表
描述符
用来描述段的详细信息,其结构如下:
图5 描述表的基本结构
段描述符由8字节组成,其中包括基地址(Base)和段大小(LIMIT)。
a)基地址:一共32位,表示段基地址。段基地址可以为4G线性空间中任意地址,注意区别于8086,80386基地址不要求16对齐。
b)段大小(LIMIT):一共20位,来表明段的大小。分两种情况:如果段的粒度为4K,则断大小为LIMIT*4K,段最大为4G字节。如果段的粒度为1字节,则段最大为1M(2^10)字节。粒度由描述符的高字第23位G来指明。
c)粒度(G):高字第23位,如果设置为1表明粒度为4K,如果设置为0,则表明粒度位1字节。
d)可用位(AVL):这些为可以为系统软件用,Intel保证这些位不会被利用。
e)提交位(P):表明该描述符是否已经被提交。如果已经被提交则置为1,否则置为0。如果为0,下图中标为可用的位就可以被系统软件赋值。
图6 提交位为0时,描述符情况
f)描述符权限级别(DPL):表明描述符访问的权限级别,被CPU用来进行权限访问控制,进行系统保护。
g)描述符类型(S):如果是系统描述符(LDT,TSS等数据段)则置为1;如果为数据段或者代码段则置为0。
h)类型(TYPE):根据描述符不同,定义各不相同。有兴趣各位可以参考Intel手册。
描述符表
描述表就是存放描述符的线性表,表中最多可以存放8K(2^13)个描述符,因为选择子索引值一共13位,所以只能索引到2^13个描述符。
一共存在两种描述符表,全局描述符表(GDT)和本地描述符表(LDT),以选择子的TI来表明,为哪一个表的选择子。
图7 全局描述附表和本地描述符表
每个系统必须有1个全局描述符表,所有的任务和程序共享全局描述符表。本地描述符表是可选的,系统中可以有一个或者多个LDT,也可以没有。比如设计为每个任务有一个自己的LDT表,也可以让多个任务共享同一个LDT。LDT也是一个段,也会在GDT中定义。
全局描述符表不是段,但是他存在于线性地址中。全局描述符表的线性地址存放在GDTR寄存器中,该寄存器开机就会被初始化。全局描述符的第一个选项不能用,表用为NULL,可以给段寄存器赋值,一访问就会报异常。
段转换总结
程序员给段寄存器赋值描述符选择子,CPU通过选择子找到段的基地址,然后加上偏移地址得出线性地址。
(未完待续)