这个分页,主要是在mit6.828的lab2的背景下来说的。
Mit6.828 Lab2:http://pdos.csail.mit.edu/6.828/2014/labs/lab2/
lab2主要讲虚拟内存->物理内存的变换,通过一定的函数来实现软件MMU的部分。
整个地址转化的过程如下图所示:
首先,明确一点,在程序里面的所有地址,都是虚拟地址,程序里面是不会出现物理地址的,就算是物理地址,CPU也会把它当做虚拟地址,通过MMU转化为物理地址。
通过上面的图,可以知道,在系统中,CPU得到一个虚拟地址,这个虚拟地址是logical address,通过分段翻译后,会得到一个线性地址(linear address),线性地址通过分页翻译后,就可以得到物理地址了。
在lab2里,分段机制基本就没有,所以可以直接认为cpu得到的虚拟地址就是线性地址,只需要经过page translation 就可以得到物理地址了。下面,就来讲一下系统分页的详细过程。
如下图所示,就是线性地址通过page director 和 page table转化成物理地址的过程:
这张图是从虚拟地址来的,所以包含了分段的过程。其实,分段过程也比较简单,以C程序举例,在C中,所有的指针,它的值是一个偏移量,这个偏移量要加上段基址,得到一个线性地址。而前面的偏移量+段基址的过程,就是分段的过程。
在一个程序中,一般分为堆,栈,代码段,数据段(.date和.bss等),而指针的值,就是相应的段上的偏移量,如一个指针指向的是一个全局变量,则它的线性地址位P+.date的基址。这个过程就是分段。在lab2里面,分段基址好像暂时没怎么涉及,所以暂且不管它。好像想在操作系统多是分页,分段应该不多了。分段和分页主要的区别就是分页的页框是固定大小的,不变的。而分段,每一段的大小都是不定的。分段有利于程序的编译。这一块还要在看看书,还是有点不了解。
分页:
在lab2中,主要就是分页的各种东西。在jos系统里面,采用二级页表的分页形式。即分为page directory 和 page table两级页表。
二级页表好处:在32位系统中,理论上来说,每个进程都拥有独立的地址空间,其大小是2^32=4G,如果采用一级页表,则以4K的页框大小为例,要分配2^20个页面来给程序使用,每个页面占4字节,则光地址转换的页面就需要4*2^20=4M大小(page table的大小)了。这对于以前比较小的内存来说,是非常的大的。虽然现在内存都有几个G,但是如果64位操作系统也用一级页表分页的话,内存塞满了都放不下转换页……。
对于大多数的程序,我并不需要使用4G的内存空间,比如编写一个非常简单的排序程序,如果数据量不是很大的话,应该总共加起来连1M的内存都用不到,这样,总共实际用到的页面就只有2^8=256个页面,占的内存只有256*4=1024=1k,剩下的大部分存这的转换部分的内存都是没用的,这就表明了一级页表的空间利用率其实是非常低的,存在着大量的浪费。
所以,要使用二级页表来进行分页。二级页表实在一级页表的基础上进一步转化而来。以32位系统为例,假设页面大小位4K,一级页表是把低位的12位作为页内偏移,高位的20位作为页索引。而二级页表,把低位的12位作为页内偏移,高位的10位作为page directory(PDE)的索引,中间的的10位作为page table(PTE)。所以,以上面那个排序程序为例,如果他只用到了部分的页面,根据程序页面的局部性原理,这些页面会集中在某几个”块“内,所以对于第一级的PDE来说,这些页面都会集中在少数的几个PDE中,只需要为那几个映射到的PDE创建相应的二级页表(即PTE),而其他没有被映射到的PDE都不用创建对应的二级页表,这样,相对于一级页表的创建所有的页面映射,二级页表只创建相应的页面映射,虽然也会有所浪费,当相对来说还是节省了大量的内存空间。
二级页表分页过程:
二级页表分页过程比较简单,首先,PDE的入口地址存储在CR3寄存器中,读取CR3寄存器的高位(20位),低12位根据线性地址的高10位得到(由于32位系统中,地址占4字节,所以每一个页表占4字节,和数组是同理:int a[10]; a[1]的地址是a[0]+4而不是+1,所以2^10个页表需要12位地址)。
通过上述过程得到的PDE,取出存在PDE中的PTE的地址,将PTE的低12位清零,然后中间10位作为索引,得到相应的物理地址的基址,最后通过低12位相加,得到对应的物理地址。转换过程不涉及很难的地方,比较简单。
这里说一下CR3,CR3保存PDE的地址,所以在进程切换之后,只需要改变CR3中的值,就可以直接切换进程的地址空间,非常的方便。
而上面的PWT表示是否采用回写策略,若PWT置位,则表示采用通写策略,即写缓存数据的同时,必须写外存。PWT=0表示回写,即写缓存数据,不必改变外存,等到缓存页面被置换,在写外存。
PCD表示是否开启缓存页。PCD=1表示禁用缓存页。PCD=0,表示启用缓存页。这个主要是因为有些页面对应的I/O端口需要实时采集数据,不能缓存数据,因为缓存的数据和实时数据有差别,所以需要禁用缓存页。这个在嵌入式里面应该出现的比较多。
最后一点,这些过程为了保证快速性,全部都是由硬件完成的,而在CR3寄存器,PDE,PGE中存储的地址,都是物理地址,而不是相对应的虚拟地址。