粗略看了一下armv8的虚拟内存的文档。记录一下,细节留待以后用到时再去细究。
程序在运行的时候使用的内存一般是虚拟内存,需要经过转换才能接触到物理内存。其中的底层支持就是硬件架构,现代架构都是支持硬件虚拟内存转换的,一般就是说提供MMU。armv8架构作为现代架构也不例外,但是不太一样的是armv8架构支持两级转换:stage 1 和stage 2.
上图包含了安全世界和非安全世界的东西,由于安全世界的东西过于复杂(主要是我也不懂),下面我们就忽略掉安全世界的东西。从上图的第四条:VA->IPA->PA.很明显,VA到PA可以最多经过两次转换,当然每个stage都是可以取消的,比如只有stage1 或只有stage2. 为什么需要两级转换呢?这是为支持虚拟化而设置的,对于虚拟机内的程序一般需要经过两级转换才能访问到主机的物理地址。所以一般地可以认为,对于主机上跑的应用程序只需要一级转换,而在虚拟机中的程序要经过两级转换。那怎样控制这些内存转换机制呢?
控制虚拟内存转换的接口就是一系列寄存器,主要有:SCTLR_EL1,TCR_EL1, TCR_EL2, HCR_EL2,TTBR0_EL1, TTBR1_EL1, VTTBR_EL2。下面分别介绍一下。
SCTLR_EL1:*系统控制寄存器,其中Mbit控制MMU 对EL0和EL1的stage 1的使能,一旦置位1,第一级虚拟内存转换就启动了。次要一点的EEbit用来控制大小端控制。
SCTLR_EL2: Mbit控制MMU对EL2的stage1使能。
HCR_EL2: VMbit控制MMU对EL0和EL1的stage2转换使能。
TCR_EL1: 转换控制寄存器。 这个寄存器直接控制EL0和EL1的第一级虚拟内存转换。
TG1, 控制TTBR1_EL1的转换颗粒大小, 也就是页大小(page size)
TG0: 控制TTBR0_EL1的转换颗粒大小
T0SZ: TTBR0_EL1的转换内存区域的总位数为2^(64-T0SZ),对于64位系统一般用到的地址不会是64位而是少于64位, 一 般是48位,比如T0SZ为16,说明需要转换的地址是48位。
T1SZ: 控制TTBR1_EL1
VTCR_EL2: 虚拟化转换控制寄存器,控制stage2的转换。
PS: 控制物理地址的大小,经过第二级转换得到的地址一定是物理地址,这一项控制输出的地址位数。
TG0: 控制VTTBR_EL2的转换颗粒大小。
T0SZ: VTTBR_EL2的转换地址的位数。
SL0: 开始转换的级数,这一位比较复杂,以后再讲。
TTBR0_EL1,TTBR1_EL1存放EL1的第一层转换页表的起始地址, VTTBR_EL2存放第二曾转换的页表其实地址。当程序要访问地址时MMU就会开始转换,第一步就是从某一TTBR系列寄存器中查询转换页面的起始地址。那么TTRBx_EL1到底怎么区分呢?请看下图:
使用某个TTBRx_EL1的原则是需要转换的地址落在哪个区域,从上图看,对于64位系统,cpu的整个地址空间分为三个部分,高地址低地址和空洞,落在低地址区域就用TTBR0_EL1,落在高地址区就使用TTBR1_EL1。高地址区的下限是2^(64-TCR_EL1.T1SZ),低地址的上限是2^(64-TCR_EL1.T0SZ)
stage1具体转换的过程如下:
stage2的转换:
上图可以看SL0的作用,制定起始转换层。地址转换流程不是唯一的,原始地址的位数,中间地址位数,最终的物理地址位数,页大小,几层页表转换,几级转换都是可以改变的,他们之间相互关系比较复杂。比如地址位数可以是48,40位,中间地址位数可以是44,42,40,页大小有4k,16k,64k,这些又会影响到页表层数。有兴趣的可以看看文档。
这里记录一个容易搞错的问题:如果在一个虚拟机中需要使用两级内存转换,那么对于第一次访问的内存需要进行多少次转换呢?假设两级级转换都是是四层。如果你的答案是4+4=8那么恭喜你,你要涨知识了。实际是(4+1)*(4+1)-1=24.怎么会是乘法呢?这里就有一个容易忽略的陷阱,当MMU去访问TTBR找到页表存放的地址时他会发现这个地址并非真实的物理地址而是一个中间地址IPA,MMU可不认识IPA他没办法找到这个IPA的位置,只能求助于stage2的转换,拿到返回的物理地址去找stage1的0级页表,接着0级页表又给MMU一个IPA,MMU又得去求助stage2,于是每一次stage1的转换都会进行全部的stage2转换,最终转换的次数就变成了乘法,至于公式为啥长这样就需要你仔细推敲一下了。这说明对于首次访问地地址,arm的效率低的出奇,连内存访问都这么慢的的arm的虚拟化性能岂不是大打折扣。还好有TLB的帮助,若非如此恐怕arm的虚拟化可以消失了。
TLB确实是个好东西,他是MMU的缓存也就是把之前访问过的从VA到PA的转换结果给记录下来方便下次再用,于是对于第二次访问的内存就无需那么多次转换了,一次就可以得到PA。而且为了提高访问效率,arm也做了一番优化。一般TLB时很小的,那么多的虚机那么多的程序都要用TLB,一般的架构会在程序被调度后很可能改变TLB的内容,那么以前用过的数据就没了,比如程序1使用VA1->PA1,程序2使用VA1->PA2,那么原来的那条就得改了,下次程序再执行的时候就得重新MMU了。但是arm可以避免这种情况,他再每个TLB前加了一个tag:ASID,用来标识每个程序,这样因为两个程序的tag不一样就不会产生冲突了。但是对虚拟情况又有不同,可能两个虚机的里面的程序拥有同样的ASID那还得冲突,于是arm有加上一个VMID,用来标识每个虚机,于是VMID,ASID就能保证不同虚机同时利用TLB。这的确可以大大减少MMU的转换次数,也就相应的提高了内存访问的性能。
关于缓存部分暂时没看到太重要的,页表的属性部分也是对内存转换相当重要的,还有很多没太注意的部分,比如内存的类型,留待以后再看。