接上节,分页机制是建立在分段机制之上,与其脱离不了干系,即使在分页机制下的进程也要先经过逻辑上的分段才行,每加载一个进程,操作系统按照进程中各段的起始范围,在进程自己的4GB虚拟地址空间中寻找可有空间分配内存段,此虚拟地址空间可以是页表,也可以是操作系统维护的某种数据结构,总之此阶段的分配是逻辑上的,并没有真正写入物理内存。代码段和数据段在逻辑上被拆分成以页为单位的小内存块。这时的虚拟地址虚如其名,不能存放任何数据。接着操作系统开始为这些虚拟内存页分配真实的物理内存页,它查找物理内存中可用的页,然后在页表中登记这些物理页地址,这样就完成了虚拟页到物理页的映射,每个进程都以为自己独享4G地址空间。
以上在宏观上笼统地介绍了分页机制下操作系统加载用户进程的整个流程,先让大家心中有数,了解我们下面所说的内容是什么。也许您对此过程并不十分理解,不过没关系,下面咱们开始从头说起。
映射这个概念大家应该比较清楚,对应的英文单词是map,意为地图。地图是对实际地理空间的一种抽象,地图上的每个位置都代表某个真实地理空间,这种地图上与地理上一一对应的关系就称为映射。
在内存地址中,最简单的映射方法是逐字节映射,即一个线性地址对应一个物理地址。比如线性地址为0x0,其对应的物理地址可以是0x0、0x10或其它你喜欢的数字,若线性地址为0x1,对应的物理地址为0x1、0x11或其它你喜欢的数字。我们需要找个地方来存储这种映射关系,这个地方就是页表,Page Table。页表就是个N行1列的表格,页表中的每一行(只有一个单元格)称为页表项PTE(Page Table Entry),其大小是4字节,页表项的作用是用来存储内存物理地址。当访问一个线性地址时,实际上就是在访问页表项中所记录的物理内存地址。
页表与物理内存关系示意如下图所示:
如果采用这种线性地址与物理地址一一映射的方案:
- 表中就应该有4G个页表项
- 32位的地址要用4字节的页表项来存储,页表总共大小是4Byte*4G =16GB。
分页机制本质上是将大小不同的大内存段拆分成大小相等的小内存块。以上方案其实就是将4GB空间划分成4G个内存块,每个内存块大小是1字节。页表也是存储在内存中的,为了表示32位地址,每个页表项必须要4字节,若按此方案,光是页表就要占 16GB内存,得不偿失,显然方案不合理。
以上方案不成立的原因是内存块数量太大了,也就是说,在总的4GB地址空间恒定不变的情况下,内存块尺寸选的太小了。为了找到合适的内存块大小,我们做下列分析与尝试。
任意进制的数字都可以分成高位部分和低位部分,若将低位部分理解为单位大小,高位部分则是这种单位的数量。如六万的十进制可表示为60000,也可以表示为60千。也就是将60000分成高位60和低位1000两部分。
为了节省页表空间,势必要将滑块往左调整,以使内存块尺寸变大,这样内存块数量变小,从而减少了页表项数量。如果滑块指向第20位,内存块大小则为2的20次方,即1MB,内存块数量则为2的12次方,即4K个。若滑块指向第12位,内存块大小则为2的12次方即4KB,内存块数量则为2的20次方,1M,即1048576个。这里所说的内存块,其官方名称是页,cpu中采用的页大小恰恰就是4KB,也就是上图中滑块的落点处。