虚拟内存无处不在:搞偏底层一点语言的同学可能经常碰到segment fault这类异常,这反映的是vm对物理地址进行保护;而使用java这样对内存使用挥金如土的语言的同学,又会碰到频繁swap的问题,这反映的是vm把物理内存当cache用,总之样样都有它的影子;本文主要讲虚拟内存如何把主存当做磁盘上地址空间的快取,以及虚拟内存上的地址到实际物理地址的转换
一.虚拟内存用作高速缓存
以32位系统为例,那么虚拟地址空间是4G;从高速缓存的角度上讲,硬盘上的空间+内存条上的空间最大应可以增加到4G,假设每页大小是4KB,则该虚拟内存总共有1M个页面,于是我们可以得出下图的结构:
1.左边那个常驻内存的叫页表,总共有1M项,每项包含一个bit的valid位,和页面地址——指向内存的or指向硬盘的or此页尚未分配
2.右下边的图描述的是uncached状态的页面,页面储存在硬盘上
3.右上角的描述的是cache状态的页面,页面储存在内存上
-
缓存命中:
如上图所示,我们需要页面2(VP2),于是在页表中查找PTE2,发现其valid为1,缓存命中,根据页面地址在内存中定位到页面
-
缓存miss:
如上图所示,我们需要页面VP3, 于是在页表中查看PTE3, 发现其valid为0,于是要的页面在磁盘上躺着,为了彰显局部性,我们得把VP3整块的读取出来,这个过程叫page in,而内存只有那么大,为了给VP3腾地儿我们选择将VP4拷贝回硬盘,这叫swap out
一些误解:
1.书上说,虚拟内存是组织在硬盘上的一串连续的byte数组,因此硬盘空间小于4G系统将无法分配足够的虚拟内存
答:只要硬盘上的加上内存上的有4G即可,会有这种想法的同学主要是还没理解页表的功能,PTE保存的是指向内存or硬盘的地址,只要两者加起来够虚拟内存就可以了;书上那么说是从“把内存当做硬盘的cache”的角度出发的,硬盘上那4G并不是客观存在的,真正要使用虚拟地址时也是从页表上查找
二.虚拟-物理地址转换
高速缓存只是虚拟内存宏观上的一个功能,更细节一点的是地址转换:跑在OS上的应用程序都是使用的虚拟地址,而硬件只认物理地址,这就涉及到很复杂的逻辑了。
一级页表地址转换:
结构:假设虚拟地址32位,物理地址是31位(即内存条有2G);把虚拟地址分成低12位(页面内部偏移VPO),高20位(页面编号VPN);把物理地址分成低12位(页面内部偏移PPO),高19位(页面编号PPN);页表总共有1M项,每个页表项(PTE)由1位valid和19位PPN组成
转换过程:
-
处理器生成32位虚拟地址,并传送给MMU(cpu里面的内存管理单元)
-
MMU截取虚拟地址中20位的VPN,并据此从页表中获取PTE
-
MMU从PTE中获取19位的PPN,与12位VPO(PPO)拼接在一起得到最终的物理地址
在得到物理地址后,就能从cache体系中读取到虚拟地址存放的字了,请注意虚拟页面跟物理页面是相等的,因此VPO等于PPO
如图所示,这里的n=32,p=12,m=31
使用TLB加速地址转换:
在上文转换过程的第2步,MMU还要从主存中获取页表,为了加快速度,很多硬件厂商直接在MMU增加了一个针对页表的cache——translation lookaside buffer
tlb是相连度较高的的组相连高速缓存,其中每行只有一个元素,所以不存在偏移的说法(关于高速缓存请参阅http://blog.chinaunix.net/uid-26726125-id-4118381.html)
如图,我们将VPN分成两部分,低位TLBI用作组索引,高位TLBT作为tag位,通过这两部分到tlb高速缓存中获取PTE
多级页表地址转换:
在上面的一级页表转换中,单个PTE大约3byte,总数量共有1M个,因此一个页表就占了3MB连续的地址空间,而且更恶心的是,这个页表你还不能把它放在比较低level的cache中,因为地址转换随时要用到它,慢谁也不能慢它。。。
用多级页表就能解决这个问题,我们将上例中20位的VPN分为相等的两个部分VPN1和VPN2,VPN1用于在一级页表中查找PTE,VPN2用于在二级页表中查找,于是一级页表就只需要保存1K个PTE即可,相应的大小也不会超过4KB,很容易就塞进L1里面,而二级页表根据实际需要创建,最多创建1K个,每个也是保存1K个PTE;转换的时候,先根据VPN1在一级页表的找到对应的PTE,而这个PTE保存的是一个二级页表的地址,再用VPN2到这个二级页表中查找PTE,这个保存的就是物理页的地址了,再拼接上虚拟地址中的VPO,就能得到最终的物理地址
实战:端到端的地址翻译
说了这么多理论知识,我们来走个全流程:将一个虚拟地址转换成物理地址,并从cache中读取一个字(假设为1byte)
设虚拟地址为14位,页面大小64byte,物理地址12位,TLB是一个4行4组的组相连高速cache,L1是一个16组直接映射的高速cache
于是得出如下结构
页面大小64可得出VPO为6,而且PPO=VPO
tlb有4组,于是可得知TLBI占2位,进而剩下TLBT占6位
L1快取每组4字节,共16组,与之对应:物理地址的CO占2位,CI占4位,剩下的CT占6位
现在已知虚拟地址0x03d4,CPU需要读取在这个地址上的字
分拆虚拟地址查找物理地址
根据TLBI和TLBT到tbl缓存里查找,发现命中PTE,而且valid位为1,于是PPN为0D,进而完成虚拟-物理地址转换,得到物理地址0x354
分拆物理地址查找缓存
根据CI和CO到L1缓存里查找,发现命中,于是返回字0x36
上面需要注意的有两点,
- 如果tlb未能命中,则需要从主存中的页表中获取PPN,速度会大打折扣,也就是我们常说的TLB miss
- CI+CO恰好等于PPO这种情况,被很多CPU厂商加以利用,用做CPU并行读取:
-
由于L1的结构已知,可以先取VPO算出CI和CO,进而锁定cache中的候选字(还缺一个tag,不能算选中了)
-
同时并发的根据VPN获取PPN:从TLB中或者多级页表中,等于最后得到就是CT了(因为CT长度恰好等于PPN长度),CT与候选字的tag进行比较,发现相等,结束