简单的分页模型
分页的内存管理模式是我们再熟悉不过的了,因为这是我们一贯的工作方式.在处理器中有负责分段管理的段部件.每个程序或任务都有自己的段,这些段都用段描述符定义.随着程序的执行,当要访问内存时,就用段地址上偏移量,段部件就会输出一个线性地址.在单纯的分段模式下,线性地址就是物理地址.
然而一旦决定采用页式内存管理,就应当把4GB内存分为大小相同的页.但是,页在物理内存中位置是有讲究的,并不是在内存中随便找个位置.页的最小单位是4KB,也就是4096个字节,用十六进制数表示就是0x1000,可以将4GB的内存划分为1048576(0x100000)个页.很显然,页的物理地址,其低12为始终是全0.
段管理机制对于Intel处理器来说是最基本的,任何时候都无法关闭.所以即使启用了页管理功能,分段机制依然是起作用的,段部件也依然工作.分页机制也没有增加程序员的负担,程序依然是按段来组织的.
问题在于,如何将较大的段,映射到大小相同的页上?
如图:
在分页模式下,操作系统可以创建一个为所有任务共用的4GB虚拟内存空间,也可以为每一个任务创建独立的4GB虚拟内存空间.档一个程序加载时,操作系统既在要左边的虚拟内存中分配段空间,又要在右边的物理内存中分配相应的页面.因此,第一个步骤是寻找空闲的段空间,该段空间既没有被其他程序使用,也没有被同一程序内的其他段使用.
在分段之后,操作系统的任务就是把段分开,并分别映射到物理页.注意,段必须是连续的,但是页不需要是连续的.事实上,在开机之后,经历过几轮分配和回收,空闲的页零碎的分布在物理内存中,一般不会是连续的了.分配页面时,操作系统会搜索那些空闲的页,并分配给程序使用,所分配的页面的总长度要大于等于段长度.
4GB虚拟内存空间不能用来保存任何数据,因为它是虚拟的,它只是用来指示内存的使用情况.当操作系统加载一个程序并创建为任务时,操作系统在虚拟内存空间寻找空闲的段,并映射到空闲的页.然后,到真正开始加载程序时,再把原本属于段的数据按页的尺寸拆开,分开写入对应的页中.
从段部件输出的是线性地址,或者叫虚拟地址.为了根据线性地址找到页的物理地址,操作系统必须维护一张表,把线性地址转换成物理地址,这是一个反过程.
因为有1048576个页,所以转换表也有相同的表项.这是一个一维表格,每个表项占4字节,内容为页的物理地址.这个表格的用法是:
因为页的尺寸是4KB,故,线性地址的低12为可用于访问页内偏移量,高20位可用于指定一个物理页.因此,把线性地址的高20为当做一个索引,乘以4,作为表内偏移量,从表中取出一个双字,那就是该线性地址所对应的页的物理地址.
当程序加载时,操作系统会首先在虚拟内存中分配段.然后,根据段需要分成多少页,来搜索空闲页面.当段较大时,按照页的尺寸分成好几个地址区段,操作系统用每个区段的首地址,取高20位,乘以4,作为偏移量访问表格,并将分配给段区域的页的物理地址写入该表项.最后,把原来需要写入每个区段的程序数据,写到对应的页中.
先根据段首地址找到对应的索引找到表项,将分配的物理页地址写入该表项中.
在页式内存管理中,页面的管理和分配是独立的,和分段以及段地址都没有关系.操作系统所要做的,就是寻找空闲页面,把它分配给需要的段,并将页的物理地址填写到映射表中.很显然,线性地址,包括线性地址空间,和页面的物理机制没有关系.
基于以上的特点,同时为了充分挖掘分页内存管理的潜力,一般来说,每个任务都可以拥有4GB的虚拟内存空间; 同时,每个任务都有自己的页映射表.
页目录,页表和页
我们知道,为了完成从虚拟地址(线性地址)到物理地址的转换,操作系统应当为每个任务准备一张页映射表.但是这张表的总大小需要4MB.没有哪个任务会真的用到所有的表项,充其量只是很小的一部分,这样就很浪费了.
我们可能会想到一种方法,比如我们先划出一小块内存给它,然后,根据需要在动态扩展.但是因为我们需要用到表的前半部分和后半部分(前半部分用作用户区间,后半部分是内核空间),所以这张表从一开始就必须完全定义,而且不可避免的要占用4MB内存空间.所以这个方法又无法实现.
为了解决这个问题,同时又不会浪费宝贵的内存空间,处理器设计了层次化的分页结构:
分页结构层次化的主要手段是不采用单一的映射表,取而代之的事页目录和页表.首先,因为4GB的虚拟内存空间对应着1048576个4KB的页,可以随机地抽取这些页,将它们组织在1024个页表内,没个页表可以容纳1024个页.页表内的每个项目叫做页表项,占4个字节,存放的是页的物理地址,故每个页表的大小是4KB,正好是一个标准页的长度.需要注意的是页在页表内的分布是随机的,哪个页位于哪个页表中,这是没有规律的.而页目录就归总了这1024个页表,每个页目录指向一个页表,页目录的大小也是4KB.由上只要任务知道了页目录的地址就可以找到所有的页地址了.
至于这样的做法为什么可以节省空间呢.主要就是因为,在建立页目录和页表的过程中,我们只需要建立程序需要的空间对应的页表就好了,就无需每次都分配4MB的大小了,虽然还存在一定的浪费现象(比如4KB页表用不完),但是相较于每次都分配4MB大小已经节省了许多了.
而且,这样的层次化分页结构是每个任务都拥有的,每个任务都有自己的页目录和页表,而在处理器的内部有一个控制寄存器CR3(又叫页目录基址寄存器,PDBR),存放着当前任务页目录的物理地址.每个任务都有自己的任务状态段(TSS),它就包括了CR3寄存器域,存放了任务自己的页目录物理地址.当任务切换时,;处理器切换到新任务开始执行,而CR3寄存器的内容也被更新,以指向新任务的页目录位置,相应的,页目录又指向一个个的页表,这就使得每个任务都只在自己的地址空间内运行.
地址变换的具体过程
段部件会输出线性地址,在没有开启分页机制时,这就是要访问的物理内存地址.如果开启了分页机制,这就是一个虚拟地址,要经过页部件的转换才能的到物理地址.处理器的页部件专门负责现行地址到物理地址的转换工作.他首先将段部件送来的32位线性地址截成3段,分别是高10位,中间的10位和低12位.高10位是页目录的索引(索引*4获得页目录内的偏移量),中间10位是页表的索引(索引*4获得页表内的偏移量),低12位则作为页内偏移来用.