深入理解操作系统(25)第十章:虚拟存储器(2)虚拟内存作为内存管理的工具+保护工具+地址翻译(虚拟内存的优点/段错误定义/PTBR/页面命中和缺页/TLB和TLB命中/多级页表/例子:端到端的地址翻译)
1. 虚拟内存作为内存管理的工具
在上一节中,我们看到虚拟存储器如何提供一种机制,利用DRAM来缓存来自通常更大的虚拟地址空间的页面。
有趣地是,一些早期的系统,比如DEC PDP-11/70,支持的是一个比物理存储器更小的虚拟地址空间。
然而,虚拟地址仍然是一个有用的机制,因为它大大地简化了存储器管理,并提供了一种简单自然的保护存储器的方法。
到目前为止我们都假设有一个单独的页表,将一个虚拟地址空间映射到物理地址空间。实际上,操作系统为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。
图10.9展示了基本概念。
图10.9
在这个示例中,进程i的页表将VP1映射到PP2(PP:physical page物理地址),将VP2映射到PP7。
相似地,进程j的页表将VP1映射到PP7,VP2映射到PP10。
注意,多个虚拟页面可以映射到同一个共享物理页面上。
按需页面调度和独立的虚拟地址空间的结合对系统中存储器的使用和管理造成了深远的影响。
特别地,VM简化了链接和加载,共享代码和数据,以及对应用分配有储器。
1.1 简化链接
独立的地址空间允许每个进程为它的存储器映像使用相同的基本格式,而不管代码和数据实际存放在物理存储器的何处。
例如,每个linux进程都使用图10.10所示的格式。
图10.10
说明:
1. 程序(文本区)总是从虚拟地址0x08048000处开始,
2. 栈总是从地址0xbfffffff向下伸展,
3. 共享库代码总是从地址0x40000000处开始,
4. 而操作系统代码和数据(内核数据)总是从地址oxc0000000开始。
这样的一致性极大地简化了链接器的设计和实现,允许链接器生成全链接的可执行文件,这些可执行文件是独立于物理存储器中代码和数据的最终位置的。
总结:
每个进程都有相同格式的虚拟地址空间,方便各个目标文件进行符号查找,引用和定义。
1.2 简化共享
独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一致机制。
一般而言,每个进程都有自己私有的代码、数据、堆以及栈区域,是不和其他进程共享的。在这种情况中,操作系统创建页表,将相应的虚拟页映射到不同的物理页面。
然而在一些情况中,还是需要进程来共享代码和数据。例如,每个进程必须调用相同的操作系统内核代码,而每个C程序都会调用标准库中的程序,比如printf。操作系统通过将不同进程中适当的虚拟页面映射到相同的物理页面,从而安排多个进程共享这部分代码的一个拷贝,而不是在虚拟存储器每个进程中都包括单独的内核和c标准库的拷贝。
1.3 简化内存分配
虚拟存储器为向用户进程提供一个简单的分配额外存储器的机制。
当一个运行在用户进程中的程序要求额外的堆空间时(例如,调用malloc的结果),操作系统分配一个适当数字(例如k)个连续的虚拟存储器页面,并且将它们映射到物理存储器中任意位置的k个任意的物理页面。由于页表工作的方式,操作系统没有必要分配k个连续的物理存储器页面。页面可以随机地分散在物理存储器中。
1.4 简化加载(存储器映射 mmap系统调用)
虚拟存储器也使加载可执行文件和己共享目标文件到存储器中变得容易。
回想一下,ELF可执行文件中的.text和.data节是相邻的。为了加载这些节到一个新创建的进程中,Linux加载程序分配了一个从地址0x08048000处开始的连续的虚拟页面区域,将它们标识为无效的(也就是未缓存的),并将它们的页表条目指向目标文件中适当的位置,
有趣的一点是:加器不会真正地从磁盘中拷贝任何数据到存储器中。
当每个页面第一次被引用时,虚拟存储器系统将自动并按需地把数据从磁盘上调入到存储器,页面引用或者是当CPU取一条指令时,或者是当一条正在执行的指令引用一个存储器位置时。
映射一个连续虚拟页面的集合到任意一个文件中的任意一个位置的概念叫做存储器映射(memory mappig)。
Unix提供了一个叫做mmap的系统调用,允许应用程序进行自己的存储器映射。
mmap参考:
https://blog.csdn.net/lqy971966/article/details/120705836
2. 虚拟内存作为内存保护的工具
2.1 保护机制
任何现代计算机系统必须为操作系统提供手段来控制对存储器系统的访问。
不应该允许一个用户进程修改它的只读文本段,而且也不应该允许它读或修改任何内核中的代码和数据结构。不应该允许它读或者写其他进程的私有存储器,并且不允许它修改任何与其他进程共享的虚拟页面,除非所有的共享者都显式地允许它这么做(通过调用明确的进程间通信系统调用)。
就像我们所看到的,
提供独立的地址空间使得分离不同进程的私有存储器变得容易。
但是,地址翻译机制可以以一种自然的方式扩展到提供更好的访问控制。
因为每次CPU生成一个地址时,地址翻译硬件都会读一个PTE,所以通过在上添加一些外的许可位来控制对一个虚拟页面内容的访问,十分简单图10.11展示了一般的概念。
图10.11
在这个示例中,我们己经添加了三个许可位到每个PTE。
SUP位表示进程是否心须运行在内核(超级用户)模式下才能访问该页。
运行在内核模式中的进程可以访问任何页面,但是运行在用户模式中的进程只允许访问那些SUP为0的页面。READ位和WRITE位控制对页面的读和写访问。
例如,如果进程i运行在用户模式下,那么它有读VP0和读写VP1的权限。然而,不允许它访问VP2。
2.1 段错误 定义
如果一条指令违反了这些许可条件,那么CPU就触发一个一般保护故障,将控制传递给一个内核中的异常处理程序。
Unix shell典型地将这种异常报告为“段错误(segmentation fault)”。
3. 地址翻译
3.1 地址翻译定义及例子说明
这一节讲述的是地址翻译的基础知识。我们的目标是让你对硬件在支持虚拟存储器中的角色有正确的评价,并给你足够多的细节使得你可以亲手演示一些具体的示例。不过,要记住我们省略了大量的细节,尤其是和时钟相关的细节,虽然这些细节对硬件设计者来说是非常重要的,但是超出了我们讨论的范围。
图10.12概括了我们在这节里将要使用的所有符号,供你参考。
图10.12
3.1.1 地址翻译定义
地址翻译是:
一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)中元素之间的映射
MAP:VAS->PAS U 0
这里:
MAP(A)=A 如果虚拟地址A处的数据在PAS的物理地址A处
=0 如果虚拟地址A处的数据不在物理存储器中
3.1.2 使用页表的地址翻译
图10.13展示了MMU是如何利用页表来实现这种映射的。
PTBR:
CPU中的一个控制寄存器,页表基址寄存器(page table base register PTBR)指向当前页表。
n位的虚拟地址包含两个部分:
一个p位的VPO(虚拟页面偏移) 和 一个(n-p)位的VPN虚拟页号
MMU利用VPN来选择适当的PTE。
例如,VPN0选择PTE0,VPN1选择PTE1,以此类推。
将页表条目中PPN(physical page number,物理页号)和虚拟地址中的VPO串联起来,就得到相应的物理地址。
注意,因为物理和虚拟页面都是P字节的,所以PPO(physical page offset 物理页面偏夥)和VPO是相同的。
图10.13
3.1.3 页面命中和缺页示例
图10.14
图10.14(a)展示了当出现页面命中时,CPU硬件执行的步骤
第一步:处理器生成一个虚拟地址,并把它传送给MMU
第二步:MMU生成PTE地址,并从高速缓存/主存请求得到它
第三步:高速缓存/主存向MMU返回
第四步:MMU构造物理地址,并把它传送给高速缓存/主存
第五步:高速缓存/主存返回所请求的数据字给处理器
和页面命中不同的是,页面命中完全是由硬件来处理的,而处理缺页要求硬件和操作系统内核协作完成。
第一步:处理器生成一个虚拟地址,并把它传送给MMU
第二步:MMU生成PTE地址,并从高速缓存/主存请求得到它
第三步:高速缓存/主存向MMU返回
第四步:PTE中的有效位是零,所以MMU触发了一次异常,
传递CPU中的控制到操作系统内核中的缺页异常处理程序。
第五步:缺页处理程序确定出物理存储器中的牺牲页,
如果这个页面己经被修改了,则把它页面换出到磁盘。
第六步:缺页处理程页面调入新的页面,并更新存储器中的PTE
第七步:缺页处理程序返回到原来的进程,驱使导致缺页的指令重新启动。
CPU将引起缺页的指令重新发送给MMU因为虚拟页面现在缓存在物理存储器中,所以就会命中。
在MMU执行了图10.14(b〕中的步骤之后,主存就会将所请求字返回给处理器。
3.2 结合高速缓存和虚拟存储器
在任何既使用虚拟存储器又使用SRAM缓存的系统中,都有应该使用虚拟地址还是使用物理地址来访问高速缓存的问题。尽管关于这个折中的详细讨论己经超出了我们的讨论范围,但是大多数系统是选择物理寻址的。
使用物理寻址,多个进程同时在高速缓存中有存储块和共享来自相同虚拟页面的块成为很简单的事情。而且,高速缓存无需处理保护问题,因为访问权限的检查是地址翻译过程的一部分。
图10.15巧展示了一个物理寻址的高速缓存如何和虚拟存储器结合起来,
主要的思路:是地址翻译发生在高速缓存查找之前。
注意:页表条目可以缓存,就像其他的数据字一样。
图10.15
3.3 利用TLB加速地址翻译
3.3.1 TLB (translation loolaside buffer)翻译后备缓冲器
正如我们看到的,每次CPU产生一个虚拟地址,MMU就必须查阅一个PTE,以便虚拟地址翻译为物理地址。
在最糟糕的情况下,这会要求一次对存储器的额外的取数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1缓存中,那么开销就下降到1个或2个周期。然而,许多系统都试图消除类似这样的开销。
它们在MMU中包括了一个关于PTE(页表条目)的小的缓存,称为TLB
(translation lookaside buffer,翻译后备缓冲器)
TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个组成的块。
TLB通常有高度的相联性。
如图10.16所示,用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号中提取出来的。
如果TLB有T=2的t次方个组,那么TLB索引(TLBI)是由VPN的t个最低位组成的,而就TLB标记(TLBT)是由VPN中剩余的位组成的。
图10.16
3.3.2 TLB命中
图10.17(a)展示了当TLB命中时(通常情况)所包括的步骤,这里的关键点是:
所有的地址翻译步骤都是在MMU上执行的,因此非常快。
步骤:
第一步:CPU产生一个虚拟地址
第二/三步;MMU从TLB中取出相应的PTE
第四步:MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存。
第五步:高速缓存/主存将所请求的数据字返回给CPU
图10.17(a)
3.3.3 TLB不命中
当TLB不命中时,MMU必须从L1缓存中取出相应的PTE,如图10.17(b)所示。
新取出的PTE存放在且TLB中,可能会覆盖一个已经存在的条目。
图10.17(b)
3.4 多级页表
背景:
到目前为止,我们一直假设系统只用一个单独的页表来进行地址翻译。但是如果我们有一个32位的地址空间、4kb的页面和一个4字节的PTE,那么我们总是需要一个4MB的页表驻留在存储器中,即使应用所引用的只是虚拟地址空间中很小的一部分。
这里为什么是4M?
因为:一个PTE4字节,32位共有4Gb大小空间,4GB除以4kb页面大小,得到4M。
对于地址空间为64位的系统来说,问题将变得更复杂。
3.4.1 两层页表层次结构
用来压缩页表的常用方法是:使用层次结构的页表。
我们使用一个具体的示例来加深你对这种方法的理解。
假设32位虚拟地址空间被分为4KB的页,而每个页表条目都是4字节,
还假设在这一时刻,虚拟地址空间有如下形式:
存储器的头2K个页面分配给了代码和数据,
接下来的6K个页面还未分配,
再接下来的1023个页面也未分配,
接下来的1个页面分配给了用户栈。
图10.18展示了我们如何为这个虚拟地址空间构造一个两级的页表层次结构。
图10.18
1.一级页表中的每个PTE负责映射虚拟地址空间中一个4M的组块。这里每个组块都是由1024个连续的页面组成的。
比如,PTE0映射第一个组块,PTE1映射接下来的一组块,以此类推。假设地址空间是4GB,1024个己经足够覆盖整个空间了。
一个页面4kb,一千个4M,一级页表PTE0映射4M个页。一共1k个一级页表,共4GB。
2.如果组块i中的每个页面都未被分配,那么一级PTEi就为空。
例如,图10.18中,组块2~7是未被分配的。
3.然而,如果在组块i中至少有一个页是分配了的,那么一级i就指向一个二级页表的基址。
例如,如图10.18所示,组块0、1和8的所有或者部分已被分配,所以它们的一级PTE就指向二级页表。
4.二级页表中的每个PTE都负责映射一个4KB的虚拟存储器页面,就像我们查看只有一级的页表一样。
注意,使用4字节的PTE,每个一级和二级页表都是4字节,这刚好和一个页面的大小是一样的。
3.4.2 多级页表的优点
这种方法从两个方面减少了存储器要求。
第一,如果一级页表中的一个PTE是空的,那么相应的二级页表就根本不会存在。
这表现出一种巨大的潜在节约,因为对于一个典型的程序,
4GB的虚拟地址空间的大部分都会是未分配的。
第二,只有一级页表才需要总是在主存中。
虚拟存储器系统可以在需要时创建,并页面调入或调出二级页表,这就减少了主存的压力。
只有最经常使用的二级页表才需要缓存在主存中。
3.4.3 使用k级页表地址翻译
图10.19述了使用k级页表层次结构的地址翻译。
虚拟地址被划分成为k个VPN和1个VPO。
每个VPNi都是一个到第i级页表的索引,其中1<i<=k。第j级页表中的每个PTE,1<j<k-1。
都指向第j+1级的某个页表的基址。第k级页表中的每个PTE都包含某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须访问k个对于只有一级的页表结构,PPO和VPO是相同的。
图10.19
访问k个PTE,第一眼看上去昂贵而不切实际。然而,这里TLB能够起作用,正是通过将页表中不同层次上的PTE缓存起来。
实际中,带多级页表的地址翻译并不比单级页表慢很多。
3.5 综合:端到端的地址翻译
3.5.1 小存储器系统寻址
在这一节里,我们通过一个其体的端到端的地址翻译示例,来综合一下我们刚学过的这些内容,
这个示例运行在有一个TLB和L1 d-cache的小系统上。为了保证可管理性,我们做出如下假设:
1. 存储器是按字节寻址的。
2. 存储器访问是针对1字节的字的(不是4字节的字)。
3. 虚拟地址是14位长的(n=14)
4. 物理地址是12位长的(m=12)
5. 页面大小是64字节(P=64)
6. TLB是四路组相联的,总共有16个条目。
7. L1 d-cache是物理寻址、直接映射的,行大小为4字节,而总共有16个组。
图10.20展示了虚拟地址和物理地址的格式,因为每个页面是2的6次方=64字节,所以虚拟地址和物理地址的低6位分别作为VPO和PPO。虚拟地址的高8位作为VPN,物理地址的高6位作为PPN。
3.5.2 小存储器系统快照
图10.21展示了我们小存储器系统的一个快照,包括就TLB(a)、页表的一部分(b)和L1高速缓存(c)。
在TLB和高速缓存的图表上面,我们还展示了访问这些设备的硬件是如何划分虚拟地址和物理地址的位的。
图10.21-a
说明:
TLB:
TLB是利用VPN的位进行虚拟寻址的。因为TLB有四个组,所以VPN的低两位就作为组索引(TLBI)。
VPN中剩下的高6位作为标记(TLBT),用来区别可能映射到同一个TLB组的不同的VPN。
图10.21-b
页表:
这个页表是一个单级设计,一共有2的8次方256个页表条目(FTE)。
然而,我们只对这些条目中的开头16个感兴趣。
为了方便,我们用索引它的VPN来标识每个PTE:但是要记住这些VPN并不是页表的一部分,也不储存在存储器中。
另外,注意每个无效PTE的PPN都用一个破折号来表示,以巩固一个概念:
无论刚好这里存储的是什么位值,都是没有任何意义的。
图10.21-c
缓存:
直接映射的缓存是通过物理地址中的字段来寻址的。
因为每个块都是4字节,所以物理地址的低2位作为块偏移(CO)。
因为有16组,所以接下来的4位就用来表示组索引(CI)
剩下的6位作为标记(CT)O
例子略。