虚拟存储器的思想是程序、数据和堆栈的大小都有可能超过物理内存大小,由操作系统把当前使用的放在内存,而不需要的放在磁盘。
而绝大部分操作系统使用的虚拟存储器技术就是分页技术。
在虚拟存储器中,程序所产生的地址为虚拟地址,虚拟地址构成了虚拟地址空间。(当然了在没有虚拟存储器的系统上,程序产生的地址就是物理地址。其实程序并不知道,只是操作系统和处理器知道。下面都是按照使用虚拟存储器的系统来说)这些虚拟地址通过MMU(内存管理单元)映射为物理地址。
采用分页机制的系统,虚拟地址空间以页面为单位进行划分,虚拟地址空间会被划分成多个等大小的页面。物理地址空间也按页面为单位进行划分每一块成为页帧,或者页框。每一虚拟页面可以随意对应到物理页框,也可以对应到磁盘的页面文件的上。
我们按照IA32的分页机制来说,标准页面大小为4K。
例如一条mov指令:mov eax,[0];
此时虚拟地址0将被发给MMU,MMU发现0属于页面0的范围内,如果页面0对应的页框号为1,那么物理地址在物理地址4096-8191范围,此时就会将4096发送到地址总线上。因为虚拟地址0的页内偏移也是0(页内偏移:在页面里的位置,比如1,的页面偏移是1,4097的页面偏移也是1,这是因为一个页面大小为4K,用虚拟地址 mod 4k就得到了页内偏移)。
就类似mov eax,[4095];mov eax,[4096],4095属于页面0,页面0对应页框1,那么物理地址为8191,而4096属于页面1的范围,如果页面1对应页框0,此时的物理地址就是0。
由上面可以看出,虚拟地址空间是连续的,而物理空间是可以不连续的。也就是说一个程序只要保证他的虚拟地址空间是连续的,它就可以正常运行。
上面说的是虚拟地址到物理地址的映射的简单情况。可是如何记录这些页面到页框的映射关系呢?(当然也有些处理器系统是页框到页面的转化)。在IA处理器上使用的是页表,就是在物理内存里有一块连续的空间,来记录这些页面到页框的映射关系。每一个页表项里都有一部分去指向页框的起始地址,还有部分记录了这个页面的属性。可以通过页面号来做索引。页面号就是虚拟地址 / 4K,得到的整数部分。
当然如果只是单一的页表,也是有问题的,如果虚拟地址空间过大,那么页表所占的空间也会很大,这时候可以采用多级页表。IA32在采用4K页面的时候就使用了2级页表,IA64使用了4级。
其实两级也很简单,最上一级就是一个总的目录指示每一个二级页表的起始物理地址,可以在页号的高几位来索引页目录项。例如IA32就是通过虚拟地址的高10位来索引页目录项,然后中间10位来索引页表项。
这样,我们就可以只将用到的虚拟地址空间的页表写入内存,而没有用到的虚拟地址空间的页表就不写入。
例如,我们正好是只用了虚拟地址0-0x3FFFFF,那么我们可以在页目录第0项指向一个页表,这个页表就只表示了虚拟地址地址0-0x3FFFFF到物理地址空间的映射关系(因为高10位为页目录索引,页目录第0项,就表示了虚拟地址高10位必为0,也就是说只有低24位有效,所以最大只能到0x3FFFFF)。
从虚拟地址到物理地址要经过两步,第一步从虚拟地址到线性地址到线性地址,第二步从线性地址从物理地址。
第一步从段描述符表描述的段基址加上段偏移生成线性地址。
IA32中线性地址高10位为页目录索引,通过此找到页表,线性地址中间10位为页表项索引,通过前面找到的页表加上这个索引,找到页表项。页表项指示着页框号,页框号加上线性地址低12位(页内偏移)就生成了物理地址。