1. 引言
这些概念性的东西往往让人摸不着头脑.网上也有很多文章,大多都看起来似是而非,本人整理了一下,做个备忘.
2. 分段机制
由于分页机制是在分段机制的基础上完成的,所以我们首先介绍分段机制.
实地址模式中,我们使用段寄存器保存段地址,使用段内变址寄存器保存段偏移地址,这两个组合以后就可以得到物理内存上的物理地址了,但是由于在保护模式中,我们需要对内存加入保护机制,就必须更多的信息,仅仅通过两个寄存器就明显不够了,于是引入了分段机制,在寻址过程中保存更多的保护信息,但是基本的原理还是类似的.
程序员熟悉和操作的依然是32位变址寄存器保存的逻辑地址,但是此时16位段寄存器保存的信息已经不仅仅是段基址了,而是被称作段标识符(或段选择符)的16bit数据,如图
16位段寄存器中的0、1位为CPL位,用来标识代码所在段的当前特权级,第2位为TI位,用来判断是GDT(全局描述符表)还是LDT(局部描述符表),剩下的高13位为描述符表项索引.
索引号,就相当于数组下标——这个数组就是”段描述符(segment descriptor)表”,段描述符具体地描述了一个段(”段”就相当于把虚拟内存,分成的若干的截).这样,很多个段描述符,就组了一个数组,叫”段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段.每一个段描述符由8个字节组成,如下图
这张表很复杂,当然可以用数据结构来定义它,不过,我们这里只关心Base字段,它描述了一个段的开始位置的线性地址.
Intel设计的本意是,一些全局的段描述符,就放在”全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的”局部段描述符表(LDT)”中.至于什么时候用GDT,什么时候该用LDT,这是由上面提到的16bit段选择符中的T1字段指定的,T1=0,表示用GDT,T1=1表示用LDT.
CPU中有三个48位寄存器:GDTR、IDTR、LDTR,分别保存了内存中GDT(全局描述符表)、IDT(中断描述符表)和LDT(局部描述符表)的起始地址和界限(高32位是描述符表起始位置的物理地址,低16位是描述符表界限),一旦通过16为段寄存器的最低位确定了描述符表的类型,就可以通过三个寄存器中的一个定位到内存中的描述表,然后通过段寄存器的高13位就可以定位到具体的描述符表项.
描述符表项中保存有该段的段基址、段界限以及各种保护信息,通过这个段基址,与变址寄存器中保存的32位逻辑地址组合就可以得到线性地址了,如果没有通过置位CPU中的CR0寄存器的最高位而启动分页机制,此时的线性地址就是物理内存的物理地址.如下图
3. 分页机制
一旦置位了CPU中CR0寄存器的最高位而启动了分页机制,我们得到的线性地址就需要通过MMU(内存管理单元)进行分页机制才能转换成物理内存上的物理地址.
分段机制存在的必然性和价值体现在描述符中加入的保护位和段界限,让段的使用更加安全,而分页机制其实是在分段机制诞生前诞生的,因为虽然计算机内存在不断增加,但是软件对内存的需求总是无止境的,所以必须要有一个机制,让需求近于无限内存的软件可以在有限的内存环境下使用,于是有了将内存分块,并且将暂时不用的块放到磁盘上的分页机制,同时这个过程对于程序开发人员来说是完全透明的.
保护模式下的分页机制也和分段机制一样提供了进一步的保护机制.
CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址.从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页.这个大数组我们称之为页目录.目录中的每一个目录项,就是一个地址——对应的页的地址.
另一类”页”,我们称之为物理页,或者是页框、页桢的.是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的.
这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间.为了节省空间,引入了一个二级管理模式的机器来组织分页单元.如下图
在32位系统中,一般使用二级页表,一级页表称为页目录表bit[31:22],页目录表的每个目录项占用4字节,共计1024个目录项,所以占用4KB内存,而每个页面恰好是4KB,所以整个页目录表占用一个页面,同时,二级页表每个表项也是4字节bit[21:12],共计1024个表项,所以也占用一个页面,即4KB内存,这样,总计可以寻址1024*1024*4KB=4GB内存,恰好是32位操作系统的线性地址空间大小.
页目录表的起始地址存储在CPU的CR3寄存器上.
由分段机制得到的32位线性地址被划分为三个部分,高10位(22-31)用来寻址页目录表的目录项,通过CR3寄存器和线性地址高10位,我们就可以得到页表(即二级页表)的起始地址,线性地址的中间10位(12-21)用来用来寻址页表的页表项,从而可以在页表中得到物理内存上的页框地址,通过线性地址的低12位,就可以确定真正的物理地址了.
这样的二级模式是否真的节约了空间?这里算一下页目录项和页表项共占空间 (2^10 * 4 + 2 ^10 *4) = 8KB,远小于上面一级页表情况下占用的4MB空间.
按<深入理解计算机系统>中的解释,二级模式空间的节约是从两个方面实现的:
- 如果一级页表中的一个页表条目为空,那么那所指的二级页表就根本不会存在.这表现出一种巨大的潜在节约,因为对于一个典型的程序,4GB虚拟地址空间的大部份都会是未分配的;
- 只有一级页表才需要总是在主存中.虚拟存储器系统可以在需要时创建,并页面调入或调出二级页表,这就减少了主存的压力.只有最经常使用的二级页表才需要缓存在主存中.——不过Linux并没有完全享受这种福利,它的页表目录和与已分配页面相关的页表都是常驻内存的.
得一提的是,虽然页目录和页表中的项,都是4个字节,32位,但是它们都只用高20位,低12位屏蔽为0——把页表的低12屏蔽为0,是很好理解的,因为这样,它刚好和一个页面大小对应起来,大家都成整数增加.计算起来就方便多了.但是,为什么同时也要把页目录低12位屏蔽掉呢?因为按同样的道理,只要屏蔽其低10位就可以了,不过我想,因为12>10,这样,可以让页目录和页表使用相同的数据结构,方便.
本贴只介绍一般性转换的原理,扩展分页、页的保护机制、PAE模式的分页这些麻烦点的东东就不啰嗦了……可以参考其它专业书籍.
4. 虚拟地址、逻辑地址、线性地址、物理地址
说了这么多,也提到了四个地址:虚拟地址、逻辑地址、线性地址和物理地址,通过和很多人交流讨论、以及在网上查阅很多blog,发现大部分人对这几个地址是很难区分开的,或者存在着很多的误区与不解,下面说说我的理解.
4.1 物理地址
这四个地址中,最容易理解的就是物理地址了,在实地址模式(因为实模式没有分段或分页机制,Cpu不进行自动地址转换)下,程序员操作的就是物理地址,顾名思义,所谓的物理地址就是物理内存上的32位地址,通过物理地址可以直接定位到物理内存上的位置,无论任何操作,最终都必须要得到物理地址才能在物理内存上进行操作.
在实地址模式下并没有另外的三个概念,所以要说到另外的三个地址,就不得不说说分段和分页机制,事实上,在上面的描述中,已经讲到了他们的区别.
4.2 虚拟地址
所谓的虚拟地址,从广义上讲,就是相对于物理地址的概念,也就是说,不是物理的就是虚拟的,因为不是物理地址的地址是无法在物理内存上定位的,所以他们都可以被称为”虚拟地址”,也就是说,从这个意义上讲,逻辑地址和线性地址都可以被称为虚拟地址,而从狭义上讲,虚拟地址指的是没有经过分页机制和分段机制转换的地址,也就是段寄存器和变址寄存器内容的组合,从这个意义上来说,虚拟地址就是类似于CS:SI这样形式的地址.
4.3 逻辑地址
逻辑地址就是上层程序员可以操作的地址,也就是变址寄存器中存储的32位偏移地址,而其他寄存器上的地址往往对于上层程序员来说是不可更改甚至是不可见的. 只有在实模式下,逻辑地址才和物理地址一致(因为实模式没有分段或分页机制,Cpu不进行自动地址转换);逻辑地址也就是在保护模式下程序执行代码段限长内的偏移地址(假定代码段、数据段如果完全一样).应用程序员仅需与逻辑地址打交道,而分段和分页机制对您来说是完全透明的,仅由系统编程人员涉及.应用程序员虽然自己可以直接操作内存,那也只能在操作系统给你分配的内存段操作.
4.4 线性地址
对狭义上的虚拟地址通过分段机制以后,可以得到段基址、段界限以及段偏移地址(即逻辑地址),段基址与段偏移地址的组合就是线性地址,线性地址可以在虚拟内存上完成定位,所以也是程序员最关心的地址, 如果启用了分页机制,那么线性地址可以再经变换以产生一个物理地址.若没有启用分页机制,那么线性地址直接就是物理地址.对于程序员来说,他们并不关注MMU如何工作以及其得到的结果,因为了解所操作的内存究竟在哪个页框中是没有什么意义的,所以他们只需要关心线性地址或者逻辑地址就可以完成全部工作了.
4.5 虚拟内存(Virtual Memory)
是指计算机呈现出要比实际拥有的内存大得多的内存量.因此它允许程序员编制并运行比实际系统拥有的内存大得多的程序.这使得许多大型项目也能够在具有有限内存资源的系统上实现.
本文引用:http://hi.baidu.com/zeyu203/item/b646c3e1fe9e2a2e5b2d6416