2020-11-25(《深入理解计算机系统》多级页表详解)

一、端到端地址翻译示例
在这里插入图片描述
从图上看,TLBI占了t位,而TLBT占了n-p-t位。

上节我们刚把TLB开了个头,多说无益,还是具体来玩个实际例子吧,具体来做一个端到端(虚拟地址到物理地址)的地址翻译示例,来统筹下之前讲的知识点。先来做如下约定:

1、老规矩,存储器按字节寻址,访问也按一字节访问;

2、虚拟地址14位长(n=14),物理地址12位长(m=12),位数少点玩起来方便;

3、页面大小是64字节(P=64),也就是说(p=6)

4、TLB是四路组相联,总共16个条目;

5、L1 d-cache是物理寻址、直接映射的,行大小为4字节,总共有16个组。

这里得先贴出各个单元内的数据快照,以便分析,首先是TLB。
在这里插入图片描述
先看上面的图,要明确一点,引入TLB就是为了进一步分级VPN的,而分解方式跟VPN本身的页表逻辑可能没有对应关系!

由于页面大小64字节,p=6,因此虚拟页偏移VPO占用6位,既然是6位,再看虚拟地址是14位长(事先约定的,所以不是32位),剩下VPN是8位,也就是说TLB需要处理28也就是256个VPN值。而TLB是四路组相联,从上到下0~3这四路,由VPN的低两位——TLBI来标识;而TLB的四组中每组有四个条目,要处理总共256个VPN,它们被分成四路,也就是说每组还要对口处理64个VPN(256÷4=64)……注意到剩了6位给TLBT,他就是所谓的标记位,正刚好就能区分26=64个VPN,因此不管某个VPN被映射到哪一组,在同一组内是不会出现VPN重复歧义现象的,由TLBI确定组,再由TLBT确定标记位,于是8位VPN就这样通过6+2的方式被TLB分解和存储!

好,那假设现在CPU要读取虚拟地址0x03d4处的值会出现什么情况?我们先把0x03d4分解成VPN+VPO形式:

转换成14位的0x03d4:

(0x03) (0xd4)
000011 11010100

转化成VPN+VPO:

VPN(0x0F) VPO(0x14)
0 0 0 0 1 1 1 1 0 1 0 1 0 0

实践发现,VPN和VPO的值与0x03d4已经完全没关系了,这是16进制表示法的弱点,虽然方便,但不如二进制来的直观。好了,接下来我们看看物理内存中页表的情况:
在这里插入图片描述
我们既然都知道物理地址是由PPN+PPO组成的,而页偏移PPO和VPO又是等价已知的,现在通过查看页表又晓得VPN的0x0F是对应的PPN是0D,那物理地址不已经出来了么等于0x0D14!如果你是CPU你这么做就傻X了,还查什么页表啊!页表存储在物理内存中,直接访问内存会消耗大量时间,MMU里明明有TLB缓存都不知道用么?好接下来看看VPN的0x0F是如何被TLB分解的:
在这里插入图片描述
哇咔咔,还记得之前分析的TLBI两位和TLBT六位的结论么?忘了自己去看加粗字,现在用上了。两个量居然都是0x03。再回看TLB的图,先找组索引TLBI的0x03刚好是第四排,而标记位TLBT的0x03刚好是该组第二个条目,并且其有效位是1,因此PPN顺利取出了0D!

如果TLB里没有怎么办?那就是TLB不命中,这时候才直接去物理内存中找PTE,就跟第一个想法一样的——奇慢!

好了,物理地址0x0D14到是获得了,你该不会想又跑到物理主存中去取值吧?别忘了这还有L1高速缓存呢:
在这里插入图片描述
行大小为4字节,总共16个组,怎么看?当然还是得先从物理地址0x0D14开始看,别忘了,先按物理地址的格式来分解:

在这里插入图片描述
分解后发现,组索引CI的值是0x05,也就是L1缓存里索引5,这一行,其中标记位为0D,与物理地址中CT的值0x0D匹配;最后看偏移量OO是0x0,没有偏移,于是终于得到我们查询了半天的数值:0x36,于是MMU将其返回给CPU——注意,此例中完全没有物理内存的访问过程!

2020-11-26所补充
对上面的补充:
【0】写在前面-为什么需要虚拟存储器?
0.1)定义:虚拟存储器其实就是借用了磁盘地址空间,还记得当初我们安装CentOS,划分的swap 文件系统吗?
0.2)VM简化了链接和加载、代码和数据共享,以及应用程序的存储器分配:(摘自CSAPP)
(1) 简化链接: 每个进程都拥有独立的虚拟地址空间, 且空间范围一致;(它是可重定向目标文件使用相对物理地址的前提)
(2) 简化加载: 加载器从不实际拷贝任何数据从磁盘到存储器。每个页初次被调用哦时, 要么是CPU取指时引用, 要么是一条正在执行的指令引用一个存储器位置时引用,VM系统会按需自动调入数据页;
(3) 简化共享: 多个虚拟页面可以映射到同一个共享物理页面上;
(4) 简化存储器分配: 当需要额外的堆空间, os分配连续的虚拟存储器页面,这些VP可以映射都任意的物理页面,这些物理页面可以任意分散在存储器中;
0.3)我还想多问一句,为什么有了高速缓存,还需要TLB-translation lookaside buffer,翻译后备缓冲器呢?
**Reason: **
引入局部性原则: (摘自CSAPP)
局部性原则保证了在任意时刻, 程序将往往在一个较小的活动页面集合上工作,这个集合
叫做工作集(working set)或者常驻集(resident set)。

换句话说, 局部性原则揭示了一个现象:在一段时间内,我们会反复调入或调入同一个或
几个虚拟页页面;而且,每次CPU产生一个VA时, MMU就必须查阅PTE,
以便将VA翻译为PA, 注意是每次,所以开销很大;

解决方法: 为了消除这样的开销,在MMU中包括了一个关于PTE的小缓存,称为翻译后备缓冲器;
关键点: 所有的地址翻译步骤都是在芯片上的MMU中执行的, 因此执行速度非常快;
你要知道计算机中共有7级存储结构,访问CPU中的存储空间(MMU)的速度比访问缓存的速度可是快了几个数量级的。

【1】说了这么多,看个荔枝(以下TLB + 页表 + 高速缓存 是我们手动模拟的)(图片摘自CSAPP):

在这里插入图片描述
在这里插入图片描述
【2】题目:说有虚拟地址 0x03d7, 虚拟存储器系统如何将其翻译成物理地址和访问缓存的。
【3】解答:将以上虚拟地址用二进制表示,如下:
在这里插入图片描述
我们看到:
VPN=bit13~bit6 =0x0f;
VPO=bit5~bit0 = 0x17;
TLBT(行索引or标记)=bit13~bit8=0x03;

(这里,为什么我管标记叫做行索引,说到本质,叫其行索引,并没有什么不妥,
因为本实例中,cache采用的是直接映射,即每个组就只有一行,所以行索引在此处无意;
但若cache是采用组相联映射或全相联映射的话,每组就有多行,行索引就起到作用了);

TLBI(组索引)=bit7~bit6=0x3;

相关声明declaration写在前面:
d1) 我们这里是考虑命中的情况,当然,如果不命中, MMU需要从主存中取出相应的PTE;
d2) PS: 命中与否,是看TLB中是否有请求的PTE;
d3)虚拟地址14位,而物理地址12位;

翻译过程: (干货)
(1) MMU(MMU存在于CPU中,是硬件)从虚拟地址中抽取VPN=0x0f;
(2) 再从VPN中抽取出TLBT(行索引)=0x03, TLBI(组索引)=0x3,用于索引翻译后备存储器TLB;
(3) 带着TLBT和TLBI 查看TLB,发现第3组, 有标记位03(当然,这是手动设置的,便于模拟),且有效位=1,故命中;
(4) 命中后,将PPN=0D返回给MMU;
(5) MMU将PPN=0x0D=bit11~bit6 和 虚拟地址的VPO=0x17=bit5~bit0 连接起来,形成物理地址PA 》》 0x357
在这里插入图片描述
6) 如上图所示:我们得到了CT-Cache Tag=0x0D, CI-Cache Index=0x5, CO-Cache Offset=0x3;即得到了缓存标记CT=0x0d,缓存组索引CI=0x5,缓存偏移CO=0x3;
(7) 依据CT、CI、CO,查询高速缓存(c图), 第5组的标记位-0x0D, 故命中;
(8) 在看缓存偏移是0x3,所以取出块3字节0x1D;
对于CT + CI + CO, 我再说的明白一点: CT就是行索引, CI就是组索引, CO就是块索引;

二、多级页表
多级页表作用:通过只为进程实际使用的那些虚拟地址内存区请求页表来减少内存使用量(出自《深入理解Linux内核》第三版51页)

在解释多级页表之前,先来梳理下概念,免得后面的内容看晕:

1、页表:缓存了很多页面信息(有效位+物理页号)的表;

2、各页面信息分别由页表中每个PTE记录,

3、页面:就是对应虚拟存储器(磁盘空间)中各空间

4、页号(PN):MMU根据虚拟页号VPN索引页表对应的具体PTE,从中提取出其中的物理页号PPN,一般都是数组直接对应关系

好了,既然概念解释清楚了,那么页表大小页面大小应该不会再搞混了吧:页面大小对应虚拟存储器每个分页大小,而PET的大小就是存储一条对应地址翻译信息所占用的空间大小,那么页表大小就是该页表内所有PTE大小的总和。

我们再来回忆下,一个32位系统,有232个地址,每个地址标识一个字节的数据,那么地址能访问到的数据就涵盖了232字节,也就是(22) * (210) * (210) * (210)B数据,也就是4GB数据。

如果我们每个末级页面的大小是4kB,那么4GB的数据就被分割成1M个页面(注意单位是”个“不是字节,说的页面个数呢!这里按210进阶,不是103),好,如果我们又知道每个PTE的空间开销是占4字节大小,也就是说页表中处理每个页面都需要占用4B,处理1M个页面的页表自然就要在物理内存中占用4MB的空间……教材上确实没分析得这么明显,我如此讲解应该立马知道,4MB驻留空间是肿么得来了吧!天~4MB大小的页表,检索起来效率一定很低,如何设计操作系统才能把页表缩小以改进性能呢?(注意不是在讨论页表里每一项页面所映射的4kB空间)

好了好了,先否定掉刚才4MB末级页表规模的提案,因为现在有另一个假设也是4MB,那就是,如果我们把4GB总的虚拟存储器空间,先等分成一份份4MB大小(而不是之前的4kB大小),那就会分出1k份出来,也就是1024份空间。如果我们有一个专门的页表(I),其中刚好有1024个PTE,就刚好能标识这1024份空间。但是呢,总觉得这1024个4MB的末级页面空间(注意不是页表空间)每个都太大了,也就是VPO太大了,难以精确定位其偏移量,于是我们联想到之前在设计单级页表结构中,末级页面大小本来想分成4KB(就是一个页面能缓存的空间大小),如果我们把每份4MB空间再细分成1024个末级页面映射,完全可以在每份4MB空间中再设立另一个专门的页表(II),其中也刚好有1024个PTE,就刚好可以把末级页面映射成4KB大小。而且既然(I)中每个PTE对应一份4MB空间,而每份空间又对应一个(II),那么(I)中每一个PTE就可以和某一个(II)建立联系,于是多级页表的概念产生了!……也许当年最早的os设计者就是沿着这种思路思考出来二级页表的概念:
在这里插入图片描述
先不管最左边那些乱七八糟的虚存分配,先看最右边,从VP0~VP2047这2k个页(注意是页不是字节!),我们发现,每一个页VPx都被二级页表中的一个PTE所对应,而一个完整的二级表有1024个PTE分别对应1024个VP,图中所谓的2k个页需要两个二级页表才能对应完成。而我们又知道一个末级页面是4KB大小,那1024个末级页面总共就是4MB大小,也就说每个二级页表都对应4MB大小的数据!好了,再看一级页表,它每个PTE对应的就是这样一个二级页表,也就是说,一级页表每个PTE都对应4MB大小的数据,那么总共1024个PTE的一级页表,刚好可以完整对应4GB大小的空间!
我们发现,所谓的两级页表层次结构,是不是很像三维数组int PT[1024] [1024] [1024]?由于末尾是int型占4字节,末级页面大小就是1024个4字节也就是4kB。
好了,为啥要整二级页表呢?最起码有三点好处。其一,如果一级页表的PTE为空,那就不存在对应下标的二级页表,能节省大量潜在的空间;其二,只有一级页表需要一直保存在物理内存中,二级页表则不然,只有经常使用的需要保存在物理内存——还是为了节省空间^^,其三,也就是上面提过的问题,这样每个页表都只有1024个PTE,由于每个PTE本身只占用4B大小,因此每个页表就只占用4kB空间了!——空间空间还是为了空间!
插一句,上面说的如此设计每个页表只占4kB,和我们说的每个末级页面只占4kB,概念上完全是两回事,但如此设计却显得优雅。页表和页面的概念在这里灰常灰常容易搞混!切记不要搞混!切忌搞混!万万不能搞混!重要的事情说三遍!!
节省空间固然好,但如何在页表中查找PTE呢?比如我的页面大小是4KB,回忆下上节虚拟地址的结构是VPN+VPO,其中VPO是页面偏移,要满足4KB偏移大小,VPO就需要12位(212=1k*4),那么VPN就剩下32-12=20位可用,10位刚好可表示1024个值,于是我用10位去标识一级页表中1024个PTE,再用10位去标识每个一级页表PTE中存储的二级页表中的1024个PTE,于是这样划分的虚拟地址就能将上图的页表层次结构全部标识清楚:

VPN1(10) VPN2(10) VPO(12)

在这里插入图片描述
有没有瞬间觉得虚拟地址被玩坏的感觉?k级虚拟地址对应k级页表,多么高大上又朴实无华简易的思想!

猜你喜欢

转载自blog.csdn.net/CSNN2019/article/details/110148742