一、概括对比下8086和80386的寻址
8086的逻辑地址由16位的段地址和16位的段内偏移组成。寻址方式:段地址由用户程序指定,保存在段寄存器中。以后程序直接使用段内偏移地址(就是段内的偏移量)就行了。内存寻址时(如何得到物理内存地址): 1.取得相应的段地址(保存段寄存器中)。 2.取得偏移地址(这个一般都是指令中给出的)。 3.经过一个运算规则A 4.得到物理内存地址
80386的逻辑地址是也分两部分:16位段选择子+32位段内偏移。寻址方式:逻辑地址(这里用虚拟地址合理)在程序编译时候决定(可能是操作系统,编译器,连接器给出的)。内存寻址时: 1.取得相应的段选择子(保存段寄存器中)。(段选择子其实还是在16位段寄存器中。只是名字改了而已。后面会说明为什么) 2.取得偏移地址(这个一般都是指令中给出的)。 3.经过一个运算规则B 4.得到物理内存地址(如果没有分页)
从上面可以看出从8086到80386段内存管理是本质上是一样的。都是通过段寄存器和偏移地址来寻址:寻址方式可表示成段寄存器:[段内偏移] 不同的地方是从8086到80386上面的步骤3中的运算规则和偏移地址长度改变了而已。
现在具体写下步骤3中的地址运算规则: 运算规则A(8086): 20位的物理地址 = 段地址(16位)*10h + 段内偏移(16位) 这就是传说中的段地址左移四位+偏移地址形成20位物理地址。运算规则B(8386): 1.根据段选择子找到段基地址(32位) 2.32位物理地址 = 段基地址(32位) + 段内偏移(32位) 之所以叫80386改名叫选择子时因为段基地址是由段寄存器中的内容选择出来的。其实这一切还是很简单。要说复杂那就是80386多了些计算步骤而已。但是不用担心。这些寻址由硬件完成不用你自己计算。
最后要说明的是80386还多了一个分页的功能,这个功能是可选的。要是启用分页,上面得到地址其实是线性地址,还要变换才能形成物理地址。
二、相关寄存器和数据结构
这个地方可以先随便看看(但是很重要的其实),不懂的绕过。等看到看下面可以返回来查阅。
Descriptor 描述符一个固定结构的结构体(struct)长度是8字节也就是64位,那么这个结构体是干嘛的呢?用来描述一个段基地址。这个结构体内容包括段基地址,段长度,段属性。这个被描述的段基地址就是上面我们说的 32位物理地址 = 段基地址(32位) + 段内偏移(32位) 中的段基地址。描述符有两种,也就是说这种结构体有两种不同的结构(其实其中包含的内容都几乎一样只有一些细小的差别)。一种用来描述数据段,代码段和堆栈段的,称为非系统描述符,另一种就是用来描述LDT和TSS的,(LDT和TSS在后面有说明)称为系统段描述符。
GDT 全局描述符表(Global Descripter Table) 顾名思义,就是一个表而已。这个表存储在内存中,相当于一个结构体数组。数组的每一项就是上面所说的描述符了。这个表中可以包含四种信息的描述符。1.全局的数据段代码段堆栈段,这些段一般操作系统内核使用。(每个单独的用户任务由LDT描述,LDT后面有说明) 2.对LDT的描述 这个描述符的基址就是是LDT所在内存中的起始地址 3.对TSS的描述 这个描述符的基址是TSS所在内存中的起始地址 4.一些门描述符(调用门,中断门...) 其中#1用的是上面说的非系统段描述符,#2,#3,#4用的是系统段描述符。不同门买描述符,LDT,TSS虽然都用系统段描述符但是其中一些属性值用拉区分不同的描述符。 BTW:LDT,TSS都是属于每个任务的所以一般成对出现在GDT中。
GDTR 全局描述符表寄存器(Global Descripter Table Register) GDTR就是一个寄存器,和AX BX.. CS DS...一个概念。这个寄存器共有48位。其中32位地址指出GDT在内存中的位置(可以说这个是一个32位的物理地址,如果没有分页开启),另外16位为GDT的大小(GDT最多可以有多少项)。
LDT 局部描述符表(Local Descripter Table) 和GDT的结构差不多,所不同的是LDT只是包含对具体每个用户任务的代码段堆栈段数据段描述(当然就没有包含对TSS,和LDT的描述项了).
LDTR 局部描述符表寄存器(Global Descripter Table Register) LDTR和GDTR一样也是寄存器,不同的是它只有16位大小。为什么不和GDTR不是48位呢?原因如下: 1.GDT在一个多任务系统中一般只有一个,所以基址由一个GDTR确定,之后几乎不会改变。但是LDT的数量是可以和用户任务相等的,就是每个用户任务都可能拥有自己的LDT,这里LDTR不方便和GDTR一样使用一个48位的寄存器给出基址,因为任务的切换这个之前基址就丢失了。任务就切换不回来了。或者任务切换回来重建自己的LDT,效率很低。 2.LDTR中存放的是一个16位选择子,根据这个选择子去在GDT中寻址找到LDT的基址。换句话说:全局描述符表(GDT)的基址是存在GDTR中的(就是说GDT靠GDTR定位),而局部描述符表(LDT)的基址是存在GDT中的,作为GDT这张表中的一项描述的(注意,前面我们说过GDT中描述符项包括对LDT段的描述)。 LDTR当作为选择子,LDT作为一项在GDT中描述好处在于:每个用户任务的局部描述符表(LDT)的起始内存地址放在GDT中,这样切换任务的时候只要改变选择子就能实现LDT的切换,而且由于每个LDT在GDT中都有描述,不会丢失,方便任务切换回来。
TR 任务寄存器(Task Register) 是一个16为的选择子,作用和LDTR类似,都是用来索引全局描述符表(GDT)中的一项。所不同的是TR选择的描述符描述的不再是LDT的段了。TR选择的描述符描述的一个任务状态段(TSS:Task Status Segment)。
TSS 任务状态段(Task State Segment) 正如前面所说,任务状态段(TSS)是在GDT中描述的。任务状态段是做什么的呢?任务状态段就是内存中的一个数据结构。这个结构中保存着和任务相关的信息。当发生任务切换的时候会把当前任务用到的寄存器内容(CS EIP DS SS...)包括LDT的选择子等保存在TSS中以便任务切换回来时候继续使用。
其实除了上述寄存器以外,还有一些不可访问的隐藏寄存器,这些隐藏寄存器其实是高速缓冲寄存器,可以忽略它的存在。为了降低理解复杂度上面没有写出。将在下面部分给出解释。
Selector 选择子按照用途共有三种(其实就是一种,因为格式完全一样), 1.TR中的选择子,用来从GDT中选择一个TSS的描述符。 2.LDTR中的选择子,用来从GDT中选择一个LDT的描述符。 3.用户程序中的逻辑地址(虚拟地址,这个地址48位 = 16位选择子+32位偏移地址)中包含的选择子。用来选择程序用到的段在LDT(或者GDT)中的描述符(数据段,代码段...)。这个选择子由编译或者连接或者操作系统决定的.
三、分页
CR0中保存一个页基址A,线性地址经两级页变换成物理地址:具体见下图(摘自80386 manual).
线性地址从高到底被分成三个部分高10位B,中间10位C,末12位D。变换过程不再描述(语言描述可能看上去复杂,其实很简单)。这里给出一个变换公式比较明了: 页基址 = A; 页基址 = 页基址 + B * 4; /* 查一级目录页表,在(页基址+B*4)处取得二级页表的的基址,这里等号的意思是查表 */ 页基址 = 页基址 + C * 4 ; /* 查二级目录页表,在(页基址+B*4)处取得物理基址,这里等号的意思是查表 */ 物理地址 = 物理基址 + D; 为什么要X4?因为页表按4位对齐的。页目录项后四位不用。顺便说下选择子去GDT/LDT中索引是:描述符地址 = Base+Selector*8.为什么要X8?道理类似,GDT/LDT中一项8字节。
四、80386内存管理和任务切换(具体过程)
一个80386操作系系统中运行两个用户任务A和B 具体如下图:
这个过程没有画出包括上门说的不可访问的隐藏寄存器。实际上,TR,LDTR之后都有一个64位(内容和描述符一样)外部不可访问的高速缓冲寄存器。内容为当前选择子对应的描述符。这样只有在切换任务的时候才去内存中GDT索引。具体任务在执行代码或者数据寻址时CPU直接读取高速缓冲寄存器中保存的某个和TR或者LDTR相对应的描述符。
任务A运行
任务B运行
源文档 <http://blog.csdn.net/fancylea/archive/2008/07/07/2622501.aspx>