深入理解虚拟内存

虚拟内存的产生

尽管基址寄存器和界限寄存器可以用于创建地址空间的抽象,还有另一个问题需要解决:管理软件的膨胀(bloatware)

需要运行的程序往往大到内存无法容纳,而且必然需要系统能够支持多个程序同时运行,即使内存可以满足其中单独一个程序的需要,但总体来看,它们仍然超出了内存大小。交换技术 (swapping)并不是一个有吸引力的解决方案,因为一个典型的SATA磁盘的峰值传输率最高达到 100MB/s,这意味着至少需要10秒才能换出一个1GB的程序,并需要另一个10秒才能再将一个1GB的程序换入

在20世纪60年代所采取的解决方法是:把程序分割成许多片段,称为覆盖(overlay)。程序开始执行时,将覆盖管理模块装入内存,该管理模块立即装入并运行覆盖0。执行完成后,覆盖0通知管理模块装入覆盖1,或者占用覆盖0的上方位置(如果有空间),或者占用覆盖0(如果没有空间)。一些覆盖系统非常复杂,允许多个覆盖块同时在内存中。覆盖块存放在磁盘上,在 需要时由操作系统动态地换入换出

虽然由系统完成实际的覆盖块换入换出操作,但是程序员必须把程序分割成多个片段。把一个大程序分 割成小的、模块化的片段是非常费时和枯燥的,并且易于出错。很少程序员擅长使用覆盖技术。因此,没过多久就有人找到一个办法,把全部工作都交给计算机去做

采用的这个方法(Fotheringham,1961)称为虚拟内存(virtual memory)。虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称作一页或页面(page)。每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令

从某个角度来讲,虚拟内存是对基址寄存器和界限寄存器的一种综合。8088为正文和数据分离出专门的基址寄存器(但不包括界限寄存器)。而虚拟内存使得整个地址空间可以用相对较小的单元映射到物理内存,而不是为正文段和数据段分别进行重定位

虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用

虚拟地址的实现

分页

大部分虚拟内存系统中都使用一种称为分页(paging)的技术,我们现在就介绍这一技术。在任何一台计算机上,程序引用了一组内存地址。当程序执行指令

MOV REG,1000

它把地址为1000的内存单元的内容复制到REG中(或者相反,这取决于计算机的型号)。地址可以通过索引、基址寄存器、段寄存器或其他方式产生

由程序产生的这些地址称为虚拟地址(virtual address),它们构成了一个虚拟地址空间(virtual address space)。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有同样地址的物理内存字;而在使用虚拟内存的情况下,虚拟地址不是被直接送到内存总线上,而是被送到内存管理单元(Memory Management Unit,MMU),MMU把虚拟地址映射为物理内存地址
如图所示
在这里插入图片描述
下图中一个简单的例子说明了这种映射是如何工作的。在这个例子中,有一台可以产生16位地址的计算机,地址范围从0到64K,且这些地址是虚拟地址。然而,这台计算机只有32KB的物理内存,因此,虽然可以编写64KB的程序,但它们却不能被完全调入内存运行。在磁盘上必须有一个可以大到64KB的程序核心映像的完整副本,以保证程序片段在需要时能被调入内存

在这里插入图片描述
虚拟地址空间按照固定大小划分成称为页面(page)的若干单元。在物理内存中对应的单元称为页框 (page frame)。页面和页框的大小通常是一样的,在本例中是4KB,现有的系统中常用的页大小一般从512字节到64KB。对应于64KB的虚拟地址空间和32KB的物理内存,我们得到16个虚拟页面和8个页框。RAM和磁盘之间的交换总是以整个页面为单元进行的

图中的标记符号如下:标记0K~4K的范围表示该页的虚拟地址或物理地址是0~4095。4K~8K的范围表示地址4096~8191,等等。每一页包含了4096个地址,起始于4096的整数倍位置,结束于4096倍数缺1

当程序试图访问地址0时,例如执行下面这条指令

MOV REG,0

将虚拟地址0送到MMU。MMU看到虚拟地址落在页面0(0~4095),根据其映射结果,这一页面对应的是页框2(8192~12 287),因此MMU把地址变换为8192,并把地址8192送到总线上。内存对MMU一无所知,它只看到一个读或写地址8192的请求并执行它。MMU从而有效地把所有从0~4095的虚拟地址映射到了8192~12 287的物理地址

在实际的硬件中,用一个“在/不在”位(present/absent bit)记录页面在内存中的实际存在情况

当程序访问了一个未映射的页面,例如执行指令

MOV REG,32780

将会发生什么情况呢?虚拟页面8(从32 768开始)的第12个字节所对应的物理地址是什么呢?MMU注意到该页面没有被映射(在图中用叉号表示),于是使CPU陷入到操作系统,这个陷阱称为缺页中断(page fault)。操作系统找到一个很少使用的页框且把它的内容写入磁盘(如果它不在磁盘上)。随后把需要访问的页面读到刚才回收的页框中,修改映射关系,然后重新启动引起陷阱的指令

例如,如果操作系统决定放弃页框1,那么它将把虚拟页面8装入物理地址8192,并对MMU映射做两处修改。首先,它要标记虚拟页面1表项为未映射,使以后任何对虚拟地址4096~8191的访问都导致陷阱。随后把虚拟页面8的表项的叉号改为1,因此在引起陷阱的指令重新启动时,它将把虚拟地址32780映射为物理地址4108(4096+12)

MMU的实现

下面查看一下MMU的内部结构以便了解它是怎么工作的,以及了解为什么我们选用的页面大小都是2的整数次幂

在下图中可以看到一个虚拟地址的例子,虚拟地址8196(二进制是0010000000000100)用图3- 9所示的MMU映射机制进行映射,输入的16位虚拟地址被分为4位的页号和12位的偏移量。4位的页号可以表示16个页面,12位的偏移可以为一页内的全部4096个字节编址

在这里插入图片描述
可用页号作为页表(page table)的索引,以得出对应于该虚拟页面的页框号。如果“在/不在”位是0,则将引起一个操作系统陷阱。如果该位是1,则将在页表中查到的页框号复制到输出寄存器的高3位中,再加上输入虚拟地址中的低12位偏移量。如此就构成了15位的物理地址。输出寄存器的内容随即被作为物理地址送到内存总线

页表

作为一种最简单的实现,虚拟地址到物理地址的映射可以概括如下:虚拟地址被分成虚拟页号(高位部分)和偏移量(低位部分)两部分。例如,对于16位地址和4KB的页面大小,高4位可以指定16个虚拟页面中的一页,而低12位接着确定了所选页面中的字节偏移量(0~4095)。但是使用3或者5或者其他位数拆分虚拟地址也是可行的。不同的划分对应不同的页面大小

虚拟页号可用做页表的索引,以找到该虚拟页面对应的页表项。由页表项可以找到页框号(如果有的话)。然后把页框号拼接到偏移量的高位端,以替换掉虚拟页号,形成送往内存的物理地址。 页表的目的是把虚拟页面映射为页框。从数学角度说,页表是一个函数,它的参数是虚拟页号,结果是物理页框号。通过这个函数可以把虚拟地址中的虚拟页面域替换成页框域,从而形成物理地址

加速分页的过程

我们已经了解了虚拟内存和分页的基础。现在是时候深入到更多关于可能的实现的细节中去了。在任何分页式系统中,都需要考虑两个主要问题:

  • 虚拟地址到物理地址的映射必须非常快
  • 如果虚拟地址空间很大,页表也会很大

第一个问题是由于每次访问内存,都需要进行虚拟地址到物理地址的映射。所有的指令最终都必须来自内存,并且很多指令也会访问内存中的操作数。因此,每条指令进行一两次或更多页表访问是必要的。如果执行一条指令需要1ns,页表查询必须在0.2ns之内完成,以避免映射成为一个主要瓶颈

第二个问题来自现代计算机使用至少32位的虚拟地址,而且64位变得越来越普遍。假设页长为4KB,32 位的地址空间将有100万页,而64位地址空间简直多到超乎你的想象。如果虚拟地址空间中有100万个页,那么页表必然有100万条表项。另外请记住,每个进程都需要自己的页表(因为它有自己的虚拟地址空间)

转换检测缓冲区

现在讨论加速分页机制和处理大的虚拟地址空间的实现方案,先介绍加速分页问题。大多数优化技术都是从内存中的页表开始的。这种设计对效率有着巨大的影响

多年以来,计算机的设计者已经意识到了这个问题,并找到了一种解决方案。这种解决方案的建立基于这样一种现象:大多数程序总是对少量的页面进行多次的访问,而不是相反的。因此,只有很少的页表项会被反复读取,而其他的页表项很少被访问

上面提到的解决方案是为计算机设置一个小型的硬件设备,将虚拟地址直接映射到物理地址,而不必再访问页表。这种设备称为转换检测缓冲区(Translation Lookaside Buffer,TLB),有时又称为相联存储器 (associate memory),如图所示。它通常在MMU中,包含少量的表项,在此例中为8个,在实际中很少会超过64个。每个表项记录了一个页面的相关信息,包括虚拟页号、页面的修改位、保护码(读/写/执行权 限)和该页所对应的物理页框。除了虚拟页号(不是必须放在页表中的),这些域与页表中的域是一一对应的。另外还有一位用来记录这个表项是否有效(即是否在使用)

如果一个进程在虚拟地址19、20和21之间有一个循环,那么可能会生成图中的TLB。因此,这三个表项中有可读和可执行的保护码。当前主要使用的数据(假设是个数组)放在页面129和页面130中。页面140包含了用于数组计算的索引。最后,堆栈位于页面860和页面861

在这里插入图片描述
现在看一下TLB是如何工作的。将一个虚拟地址放入MMU中进行转换时,硬件首先通过将该虚拟页号与TLB中所有表项同时(即并行)进行匹配,判断虚拟页面是否在其中。如果发现了一个有效的匹配并且要 进行的访问操作并不违反保护位,则将页框号直接从TLB中取出而不必再访问页表。如果虚拟页面号确实是在TLB中,但指令试图在一个只读页面上进行写操作,则会产生一个保护错误,就像对页表进行非法访问一样

当虚拟页号不在TLB中时发生的事情值得讨论。如果MMU检测到没有有效的匹配项时,就会进行正常的页表查询。接着从TLB中淘汰一个表项,然后用新找到的页表项代替它。这样,如果这一页面很快再被访 问,第二次访问TLB时自然将会命中而不是不命中。当一个表项被清除出TLB时,将修改位复制到内存中的页表项,而除了访问位,其他的值不变。当页表项中从页表装入到TLB中时,所有的值都来自内存

软件TLB管理

到目前为止,已经开发了多种不同的策略来改善使用软件TLB管理的机器的性能。其中一种策略是在减少TLB失效的同时,又要在发生TLB失效时减少处理开销(Bala等人,1994)。为了减少TLB失效,有时候操作系统能用“直觉”指出哪些页面下一步可能会被用到并预先为它们在TLB中装载表项。例如,当一个客户进程发送一条消息给同一台机器上的服务器进程,很可能服务器将不得不立即运行。了解了这一点,当执行处理send的陷阱时,系统也可以找到服务器的代码页、数据页以及堆栈页,并在有可能导致TLB失效前把它们装载到TLB中

无论是用硬件还是用软件来处理TLB失效,常见方法都是找到页表并执行索引操作以定位将要访问的页面。用软件做这样的搜索的问题是,页表可能不在TLB中,这就会导致处理过程中的额外的TLB失效。可以通过在内存中的固定位置维护一个大的(如4KB)TLB表项的软件高速缓存(该高速缓存的页面总是被保存在TLB中)来减少TLB失效。通过首先检查软件高速缓存,操作系统能够实质性地减少TLB失效

当使用软件TLB管理时,一个基本要求是要理解两种不同的TLB失效的区别在哪里。当一个页面访问在内存中而不在TLB中时,将产生软失效(soft miss)。那么此时所要做的就是更新一下TLB,不需要产生磁 盘I/O。典型的处理需要10~20个机器指令并花费几个纳秒完成操作。相反,当页面本身不在内存中(当然 也不在TLB中)时,将产生硬失效。此刻需要一次磁盘存取以装入该页面,这个过程大概需要几毫秒。硬失效的处理时间往往是软失效的百万倍

针对大内存的页表

在原有的内存页表的方案之上,引入快表(TLB)可以用来加快虚拟地址到物理地址的转换。不过这不是惟一需要解决的问题,另一个问题是怎样处理巨大的虚拟地址空间。下面将讨论两种解决方法

多级页表

第一种方法是采用多级页表。一个简单的例子如图所示。在图a中,32位的虚拟地址被划分为10位的PT1域、10位的PT2域和12位的Offset(偏移量)域。因为偏移量是12位,所以页面长度是4KB,共有 220 个页面

引入多级页表的原因是避免把全部页表一直保存在内存中。特别是那些从不需要的页表就不应该保留。 比如一个需要12MB内存的进程,其最底端是4MB的程序正文段,后面是4MB的数据段,顶端是4MB的堆栈段,在数据段上方和堆栈段下方之间是大量根本没有使用的空闲区

考察图b例子中的二级页表是如何工作的。在左边是顶级页表,它具有1024个表项,对应于10位的 PT1域。当一个虚拟地址被送到MMU时,MMU首先提取PT1域并把该值作为访问顶级页表的索引。因为整 个4GB(32位)虚拟地址空间已经被分成1024个4MB的块,所以这1024个表项中的每一个都表示4MB的虚拟地址空间

在这里插入图片描述
由索引顶级页表得到的表项中含有二级页表的地址或页框号。顶级页表的表项0指向程序正文的页表, 表项1指向数据的页表,表项1023指向堆栈的页表,其他的表项(用阴影表示的)未用。现在把PT2域作为访问选定的二级页表的索引,以便找到该虚拟页面的对应页框号

二级页表可扩充为三级、四级或更多级。级别越多,灵活性就越大,但页表超过三级会带来更大的复杂性,这样做是否值得令人怀疑

倒排页表

对32位虚拟地址空间,多级页表可以很好地发挥作用。但是,随着64位计算机变得更加普遍,情况发生了彻底的变化。如果现在的地址空间是264字节,页面大小为4KB,我们需要一个有252 个表项的页表。如果每一个表项8个字节,那么整个页表就会超过3000万GB(30PB)。仅仅为页表耗费3000万GB不是个好主意(现在不是,可能以后几年也不是)。因而,具有64位分页虚拟地址空间的系统需要一个不同的解决方案

解决方案之一就是使用倒排页表(inverted page table)。在这种设计中,在实际内存中每一个页框有一个表项,而不是每一个虚拟页面有一个表项。例如,对于64位虚拟地址,4KB的页,1GB的RAM,一个倒排页表仅需要262 144个页表项。表项记录哪一个(进程,虚拟页面)对定位于该页框

虽然倒排页表节省了大量的空间(至少当虚拟地址空间比物理内存大得多的时候是这样的),但它也有严重的不足:从虚拟地址到物理地址的转换会变得很困难。当进程n访问虚拟页面p时,硬件不再能通过把p当作指向页表的一个索引来查找物理页框。取而代之的是,它必须搜索整个倒排页表来查找某一个表项(n,p)。此外,该搜索必须对每一个内存访问操作都要执行一次,而不仅仅是在发生缺页中断时执行。每 一次内存访问操作都要查找一个256K的表是不会让你的机器运行得很快的

走出这种两难局面的办法是使用TLB。如果TLB能够记录所有频繁使用的页面,地址转换就可能变得像 通常的页表一样快。但是,当发生TLB失效时,需要用软件搜索整个倒排页表。一个可行的实现该搜索的方法是建立一张散列表,用虚拟地址来散列。当前所有在内存中的具有相同散列值的虚拟页面被链接在一起

如图所示。如果散列表中的槽数与机器中物理页面数一样多,那么散列表的冲突链的平均长度将会是1个表项,这将会大大提高映射速度。一旦页框号被找到,新的(虚拟页号,物理页框号)对就会被装载到TLB中

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值