深入理解计算机系统--虚拟内存

        一个系统中的进程是与其他进程共享CPU和主存资源的。然而,共享主存会形成一些特殊的挑战。随着对CPU需求的增长,进程以某种合理的平滑方式慢了下来。但是如果太多的进程需要太多的内存,那么他们中的一些就根本无法运行。当一个程序没有空间可用时,那就是他运气不好了。内存还很容易被破坏,如果某个进程不小心写了另一个进程使用的内存,他就可能以某种完全和程序逻辑无关的令人迷惑的方式失败。

        为了更加有效地管理内存并且少出错,现代系统提供了一种对主存的抽象概念,叫做虚拟内存。虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,他为每个进程提供了一个大的、一致的和私有的地址空间。通过一个很清晰的机制,虚拟内存提供了三个重要能力:(1)他将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存种只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,他高效的使用了主存。(2)他为每个进程提供了一致的地址空间,从而简化了内存管理。(3)他保护了每个进程的地址空间不被其他进程破坏。

一 物理和虚拟寻址

        计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每个字节都有一个唯一的物理地址。第一个字节的地址为0,接下来的字节地址为1,再下一个为2,以此类推。给定这种简单的结构,CPU访问内存的最自然的方式就是使用物理地址。我们把这种方式称为物理寻址。下图展示了一个物理寻址的示例,该示例的上下文是一条加载指令,他读取从物理地址4处开始的4字节字。当CPU执行这条加载指令时,会生成一个有效物理地址,通过内存总线,把他传递给主存。主存取出从物理地址4处开始的4字节字,并将它返回给CPU,CPU会将它存放在一个寄存器里。

         早期的PC使用物理寻址。然而,现代处理器使用的是一种称为虚拟寻址的寻址形式,参见下图。

         使用虚拟寻址,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址再被送到主存之前先转换成适当的物理地址。将一个虚拟地址转换为物理地址的任务叫做地址翻译。就像异常处理一样,地址翻译需要CPU硬件和操作系统之间的紧密合作。CPU芯片上叫做内存管理单元的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。

二 地址空间

        地址空间是一个非负整数地址的有序合集:

                {0,1,2,...}

如果地址空间中的整数是连续的,那么我们说他是个线性地址空间。为了简化讨论,我们总是假设使用的是线性地址空间。在一个带虚拟内存的系统中,CPU从一个有N=2的n次方个地址的地址空间中生成虚拟地址,这个地址空间成为虚拟地址空间:

                {0,1,2,...,N-1}

        一个地址空间的大小是由表示最大地址所需要的位数来描述的。例如,一个包含N=2的n次方个地址的虚拟地址空间就叫做一个n位地址空间。(一个包含N=2的2次方个地址的虚拟地址空间的最大地址是3{0,1,2,3},使用二进制表示3需要2位)。现代操作系统通常支持32位或者64位虚拟地址空间。

        一个系统还有一个物理地址空间,对应于系统中物理内存的M个字节:

                {0,1,2,...,M-1}

M不要求是2的幂,但是为了简化讨论,我们假设M=2的m次方

        地址空间的概念是很重要的,因为他清楚的区分了数据对象(字节)和他们的属性(地址)。一旦认识到了这种区别,那么我们就可以将其推广,允许每个数据对象有多个独立的地址,其中每个地址都选自一个不同的地址空间。这就是虚拟内存的基本思想。主存中的每字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。

三 虚拟内存作为缓存的工具

        概念上而言,虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每字节都有一个唯一的虚拟地址,作为到数组的索引。磁盘上数组的内容被存放在主存中。和存储器层次结构中其他缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传输单元。VM系统通过将虚拟内存分割为称为虚拟页(Virtual Page,VP)的大小固定的块来处理这个问题。每个虚拟页的大小为P=2的P次方字节。类似的,物理内存被分割为物理页(Physical Page,PP),大小也为P字节(物理页也被称为页帧(page frame))。

        在任意时刻,虚拟页面的集合都分为3个不相交的子集:

  • 未分配的:VM系统还未分配(或者创建)的页。未分配的块没有任何数据和他们关联,因此也就不占用任何磁盘空间
  • 缓存的:当前已缓存在物理内存中的已分配页
  • 未缓存的:未缓存在物理内存中的已分配页

    上图展示了一个有8个虚拟页的小虚拟内存。虚拟页0和3还没被分配,因此在磁盘上还不存在。虚拟页1、4和6被缓存在物理内存中。页2、5和7已经被分配了,但是当前并未缓存在主存中

3.1 DRAM缓存的组织结构

        为了有助于清晰理解存储层次结构中不同的缓存概念,我们将使用术语SRAM缓存来表示位于CPU和主存之间的L1、L2和L3高速缓存,并且用术语DRAM缓存来表示虚拟内存系统的缓存,他在主存中缓存虚拟页。

        在存储层次结构中,DRAM缓存的位置对他的组织结构有很大的影响。回想一下,DRAM比SRAM要慢大约10倍,而磁盘要比DRAM慢大约100000多倍。因此,DRAM缓存中的不命中比起SRAM缓存中的不命中要昂贵得多,这是因为DRAM缓存不命中要由磁盘来服务,而SRAM缓存不命中通常是由基于DRAM的主存来服务的。而且,从磁盘的一个扇区读取第一个字节的时间开销比起读这个扇区中连续的字节要慢大约100000倍。归根结底,DRAM缓存的组织结构完全由巨大的不命中开销驱动的。

        因为大的不命中处罚和访问第一个字节的开销,虚拟页往往很大,通常是4KB~2MB。由于大的不命中处罚,DRAM缓存是全相联的,即任何虚拟页都可以放置在任何的物理页中。不命中时的替换策略也很重要,因为替换了错的虚拟页的处罚也非常之高。因此,与硬件对SRAM缓存相比,操作系统对DRAM缓存使用了更复杂精密的替换算法。最后,因为对磁盘的访问时间很长,DRAM缓存总是使用写回,而不是直写。

3.2 页表

        同任何缓存一样,虚拟内存系统必须有某种方法来判定一个虚拟页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理内存中选择一个牺牲页,并将虚拟页从磁盘复制到DRAM中,替换这个牺牲页。

        这些功能是由软硬件联合提供的,包括操作系统软件、MMU(内存管理单元)中的地址翻译硬件和一个存放在物理内存中的叫做页表(page table)的数据结构,页表将虚拟页映射到物理页中。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。操作系统负责维护页表的内容,以及在磁盘与DRAM之间来回传送页。

        下图展示了一个页表的基本组织结构。页表就是一个页表条目(Page Table Entry,PTE)的数组。虚拟地址空间中的每个页在页表中一个固定偏移量处都有一个PTE。为了我们的目的,我们将假设每个PTE是由一个有效位(valid bit)和一个n位地址字段组成的。有效位表明了该虚拟页当前是否被缓存在DRAM中。如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始位置,这个物理页中缓存了该虚拟页。如果没有设置有效位,那么一个空地址表示这个虚拟页还未被分配。否则,这个地址就指向该虚拟页在磁盘上的起始位置。

         上图的示例展示了一个有8个虚拟页和4个物理页的系统的页表。4个虚拟页(VP1 VP2 VP4 VP7)当前被缓存在DRAM中。两个页表(VP0 VP5)还未被分配,而剩下的页(VP3 VP6)已经被分配了,但是当前还未被缓存。上图中有一个要点要注意,因为DRAM缓存是全相联的,所以任何物理页都可以包含任意虚拟页。

3.3 页命中

        考虑一下当CPU想要读包含在VP2中的虚拟内存的一个字时会发生什么,VP2被缓存在DRAM中。使用我们将在后续详细描述的一种技术,地址翻译硬件将虚拟内存作为一个索引来定位PTE2,并从内存中读取它。因为设置了有效位,那么地址翻译硬件就知道VP2是缓存在内存中的了。所以它使用PTE中的物理内存地址(该地址指向PP1中缓存页的起始位置),构造出这个字的物理地址。

 3.4 缺页

        在虚拟内存的惯用说法中,DRAM缓存不命中称为缺页(page fault)。下图展示了在缺页之前我们的示例页表的状态。CPU引用了VP3中的一个字,VP3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE3,从有效位中推断出VP3未被缓存,并且触发一个缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。

        接下来,内核从磁盘复制VP3到内存中的PP3,更新PTE3,随后返回。当异常处理程序返回时,他会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但是现在,VP3已结缓存在主页中了,那么页命中也能有地址翻译硬件正常处理了。下图展示了在缺页之后我们的示例页表的状态

         虚拟内存是在20世界60年代早期发明的,远在CPU-内存之间差距的加大引发产生SRAM缓存之前。因此,虚拟内存系统使用了和SRAM缓存不同的术语,即使他们的许多概念是相似的。在虚拟内存的习惯说法中,块被称为页。在磁盘和内存之间传送页的活动叫做交换(swapping)或者页面调度(paging)。页从磁盘换入(或者页面调入)DRAM和从DRAM换出(或者页面调出)磁盘。一直等待,直到最后时刻,也就是当不命中发生时,才换入页面的这种策略称为按需页面调度(demand paging)。也可以采用其他方法,例如尝试着预测不命中,在页面实际被引用之前就换入页面。然而,所有现代系统都使用的是按需页面调度的方式。

3.5 分配页面

        下图展示了当操作系统分配一个新的虚拟内存页时对我们示例页表的影响,例如,调用malloc的结果。在这个示例中,VP5的分配过程是在磁盘上创建空间并更新PTE5,使他指向磁盘上这个新创建的页面

3.6 又是局部性救了我们

        当我们中的许多人都了解了虚拟内存的概念之后,我们的第一印象通常都是它的效率应该是非常低。因为不命中处罚很大,我们担心页面调度会破坏程序性能。实际上,虚拟内存工作的相当好,这主要归功于我们的老朋友局部性(locality)。

        尽管在整个运行过程中程序引用的不同页面的总数可能超出物理内存总的大小,但是局部性原则保证了在任意时刻,程序将趋向于在一个较小的活动页面(active page)集合上工作,这个集合叫做工作集(working set)或者常驻集合(resident set)。在初始开销,也就是将工作集页面调度到内存中之后,接下来对这个工作集的引用将导致命中,而不会产生额外的磁盘流量。

        只要我们的程序有好的时间局部性,虚拟内存系统就能工作的相当好。但是,当然不是所有的程序都能展现良好的时间局部性。如果工作集的大小超出了物理内存的大小,那么程序将产生一种不幸的状态,叫做抖动(thrashing),这时页面将不断地换进换出,虽然虚拟内存通常是有效的,但是如果一个程序性能慢的像爬一样,那么聪明的程序员会考虑是不是发生了抖动。

可以使用Linux的getrusage函数监测缺页的数量(以及许多其他的信息)。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值