MMU分页原理
现代处理的分页机制往往很复杂,尤其给人的第一印象是特别复杂。为了支持操作系统对内存进行灵活高效的管理,分页机制看起来确实比较复杂,然而其本质算法还是比较简明清晰的。按照我的理解,从内存分页机制的初中到现实是这样经过几步形成的:
1. 虚拟内存空间。为了让每个进程有自己独立的内存空间,CPU需要在访存的时候进行一次地址变换:ra=f(va)。
2. 优化时间效率。访存很频繁,映射函数f一定要简单,否则会严重影响效率,因此需要简单查表算法(页表),类似于数组下标-->元素这样的变换。
3. 优化空间效率。一一映射肯定是不能接受的,于是按照一定的粒度(4KB)进行映射,粒度内的相对地址不变(低12位不变)。
4. 优化空间效率。如果只用一级页表,内存浪费还是会比较大(每个表4MB),于是将剩余的20位地址分为两部分(10位vs10位),重复3的方法,形成两级页表。如仍不能满足(对于64位系统),可再次细分。
5. 再优化时间效率。页表太大,不能全部装入处理器,只能放在RAM中。这样翻译一次地址需要多次访存,性能不可接受,于是用类似Cache一样的缓存机制最近翻译过的地址。
经过这样几步,MMU中复杂的分页机制便出炉了。然而当我们拨开现象看本质,就会发现它看起来再怎么复杂,其本质也不过就是查表映射。无论是32位系统的两级页表还是64位系统的三级、四级页表,其抽象过程都可以用数组下标到元素的映射来表示:ra=f[va]。
整体来看,地址分页映射无非就是把虚拟地址切成几部分,最后一部分用于页内偏移,其余n部分用于查询n级的一个表。不同的处理器对地址的切分方法是不同的,同一个处理器也可能支持多种不同的切分方法,如支持PAE的x86处理器就多支持一种方法,以支持多于4GB的内存。下面对常见处理器的虚地址切分方法做一个小结:
编号 | 处理器 | 页大小 | 寻址使用的位数 | 分页级别 | 虚拟地址分级 |
1 | x86 | 4KB | 32 | 2 | 10+10+12 |
2 | x86(extended) | 4MB | 32 | 1 | 10+22 |
3 | x86(PAE) | 4KB | 36 | 3 | 2+9+9+12 |
4 | x86(PAE ext) | 2MB | 36 | 2 | 2+9+21 |
5 | alpha | 8KB | 43 | 3 | 10+10+10+13 |
6 | ia64 | 4KB | 39 | 3 | 9+9+9+12 |
7 | ppc64 | 4KB | 41 | 3 | 10+10+9+12 |
8 | sh64 | 4KB | 41 | 3 | 10+10+9+12 |
9 | X86-64 | 4KB | 48 | 4 | 9+9+9+9+12 |
备注:1与2可共存,3与4可共存,5与6还支持其他分页方式,这里列出的是Linux采用的方式。 |