在早期的计算机中,要运行一个程序,会把这些程序全都装入内存,程序都是直接运行在内存上的,也就是说程序中访问的内存地址都是实际的物理内存地址。
当计算机同时运行多个程序时,必须保证这些程序用到的内存总量要小于计算机实际物理内存的大小。那当程序同时运行多个程序时,操作系统是如何为这些程序分配内存的呢?下面通过实例来说明当时的内存分配方法:
某台计算机总的内存大小是128M,现在同时运行两个程序A和B,A需占用内存10M,B需占用内存110。计算机在给程序分配内存时会采取这样的方法:先将内存中的前10M分配给程序A,接着再从内存中剩余的118M中划分出110M分配给程序B。这种分配方法可以保证程序A和程序B都能运行,但是这种简单的内存分配策略问题很多:
问题1:进程地址空间不隔离。由于程序都是直接访问物理内存,所以恶意程序可以随意修改别的进程的内存数据,以达到破坏的目的。有些非恶意的,但是有bug的程序也可能不小心修改了其它程序的内存数据,就会导致其它程序的运行出现异常。这种情况对用户来说是无法容忍的,因为用户希望使用计算机的时候,其中一个任务失败了,至少不能影响其它的任务。
问题2:内存使用效率低。在A和B都运行的情况下,如果用户又运行了程序C,而程序C需要20M大小的内存才能运行,而此时系统只剩下8M的空间可供使用,所以此时系统必须在已运行的程序中选择一个将该程序的数据暂时拷贝到硬盘上,释放出部分空间来供程序C使用,然后再将程序C的数据全部装入内存中运行。可以想象得到,在这个过程中,有大量的数据在装入装出,导致效率十分低下。
问题3:程序运行的地址不确定。当内存中的剩余空间可以满足程序C的要求后,操作系统会在剩余空间中随机分配一段连续的20M大小的空间给程序C使用,因为是随机分配的,所以程序运行的地址是不确定的。
为了解决上述问题,人们想到了一种变通的方法,就是增加一个中间层,利用一种间接的地址访问方法访问物理内存。按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样,只要操作系统处理好虚拟地址到物理内存地址的映射,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此没有重叠,就可以达到内存地址空间隔离的效果。
虚拟地址向物理地址转换的有专门的空间来存放转换的Descriptor。
ARMv8 的虚拟地址的寻址范围是48 bit。对于地址范围位于0x00000000_00000000 ~ 0x0000FFFF_FFFFFFFF之间的,要在寄存器TTBR0_EL1(Translation Table Base Register 0, 限于EL0和EL1) 寻找转换表的基址。对于地址范围位于0xFFFFFFFF_FFFFFFFF ~ 0x0000FFFF_FFFFFFFF之间的,要在寄存器TTBR1_EL1(限于EL0和EL1) 寻找转换表的基址。
4K页表的4级转换
以4K页表的4级转换为例,展示虚拟地址向物理地址的转换过程:
1.首先查看Virtual Address 的高16位VA[63:48]是否为全0,如果全0,使用TTBR0_EL1寄存内放的Level 0 Page Table的基地址; 否则,使用TTBR1_EL1.
2.由于是4K的页表,4K页表的大小是这样的计算的: 4KB = 1024 × 8 × 4 = 512 × 64 bit. 就是说4K 的页表要分为512个Entry, 每个Entry的大小为64bit。每个Entry存放的数据,实际是下一个level 的转换表的地址。对于某个VA[47:0], 我们怎么知道下一级页表的地址存放在这512个 Entry 中的那个Entry呢?答案是使用VA[47:39]来索引。这样就可以找到第二级转换表(level 1 page table)的首地址.
3.同样,Level 1 page table 也是4K 共512 个Entry,每个Entry 存放下一个页表的首地址,这个首地址的存放的位置要用VA[38:30]去索引Level 1 page table的Entry 得到. 样就可以找到第三级转换表(level 2 page table)的首地址.
4.同样,Level 2 page table 也是4K 共512 个Entry,每个Entry 存放下一个页表的首地址,这个首地址的存放的位置要用VA[29:21]去索引Level 1 page table的Entry 得到. 样就可以找到第四级转换表(level 3 page table)的首地址.
5.Level 3 page table 内存放的就是VA 向 PA转换的Descriptor了, 也是512个entry,每个Entry 64bit的数据。 通过VA[20:12]来索引使用那个Entry的descriptor。在这个descriptor中就可以得到我们想要的物理地址的 PA[47:12].
6.最终的地址转换完成,VA[47:0] 转换为 PA[47:0] = {来自level 3 转换表的PA[47:12], VA[11:0]}. 就是Descriptor 中给出物理地址的[47:12], 而虚拟地址给出物理的值的[11:0].
16K页表的的四级转换
其基本流程和4K页表的类似,不同之处如下:
16K 页表的第一级转换表只有两个Entry,使用VA[47]来索引下一级page table的首地址;
16K 页表的第二级转换表有 2^11 = 2048个 Entry, 使用VA[46:36] 来索引下一级page table的首地址;
16K 页表的第三级转换表有 2^11 = 2048个 Entry, 使用VA[35:25] 来索引下一级page table的首地址;
16K 页表的第四级转换表有 2^11 = 2048个 Entry, 使用VA[24:14] 来索引存放着PA[47:14] (output address)的地址;
16K页表转换后的物理地址: PA[47:0] = {PA[47:14], VA[13:0]}.
64K页表的的三级转换
对于ARMv8-A 来说 64K页表只有三级转换,如下所示: