内存管理之虚拟内存

       前面我们提到了关于解决内存过载的两种解决方法中的交换技术,现在我们介绍另一种解决方法:虚拟内存(Virtual Memory)。虚拟内存的基本思想是:每个程序都拥有自己的地址空间,这个空间被分割成多个块,每个块被称为页或页面(page)。每个页面有连续的地址范围。这些页被映射到内存,但并不是进程的所有页面都被映射到内存中进程才可以运行,当进程引用到一部分在物理内存中的地址空间时,由硬件立执行必要的映射。当进程引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的页面调入到内存并且重新执行失败的指令。虚拟内存非常适合在多道程序设计系统中使用,许多程序的片段同时保持在内存中,每一个程序在等待它的一部分页面读入内存的同时,可以把CPU交给另一个进程使用。不同与交换技术,虚拟内存只需要保证引用到的虚拟地址空间所在的页面被映射到内存,进程就可以执行,这样就能使得内存中有尽可能多的进程。

  • 分页

  大部分虚拟内存系统都会使用到分页(paging)技术。当执行指令MOV REG, 1000时,它将内存地址为1000的内存单元中的内容复制到寄存器中(具体功能取决于不同的机器型号)。通常我们把由程序产生的地址称之为虚拟地址(virtual address),他们构成一个虚拟地址空间(virtual address space)。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有同样地址的物理内存字(如果使用地址重定向会进行相应的地址转换)。在使用虚拟内存时,系统不再把虚拟地址直接送到内存总线上,而是先把虚拟地址送到内存管理单元(Memory Management Unit,MMU)中,MMU把虚拟地址映射成物理内存再送到内存总线上。更多关于MMU的介绍可以参见MMU虚拟地址空间按照固定大小划分成多个成为页面的单元,在物理内存中对应的单元称之为页框(page frame),页面和页框的大小通常是一致的通常每个进程都会有自己的页表(page table),页表的作用:用于记录虚拟地址空间的页面在内存中的映射情况。我们举个例子来看看映射是如何工作的。这里有一个32KB内存的计算机,地址范围是0~32K,一个64KB的程序,地址范围是0-64K。虽然可以编写比物理内存大的的程序,但不能确保程序的所有页面都被调入到内存中,在磁盘(交换空间)上必须有一个最多64KB的程序核心映像的完整副本,以便保证程序片断在需要时能够被调入内存。映射关系如下:

  当执行如下这条指令: MOV REG,0。将虚拟地址0送入到MMU,MMU看到虚拟地址0落在页面0(0~4k),根据页面0的映射结果,这一页面被映射到页框2中(8k~12k),因此这条指令就被MMU翻译成MOV REG,8192,然后再把它送到内存总线上。内存对MMU一无所知,内存只看到有一个指令要求读或写内存地址8192的请求并执行它,MMU完成了由虚拟地址到物理地址的转换同样的执行MOV REG,8192,MMU将这条指令翻译成MOV REG,24576。如果当MMU发现访问的虚拟地址的页面没有被映射到内存会怎样呢?当MMU意识到访问的页面没有被映射到物理内存,于是CPU陷入到操作系统,这种陷阱称之为缺页中断或者缺页错误(page fault)。操作系统找到一个很少被访问的页框,将页框中的内容写回到磁盘,随后调入需要访问的页面到内存,修改映射关系,然后重新执行引起缺页中断的指令。

  • 页表

  前面我们提到了页表的作用是记录进程的虚拟地址空间和物理内存的地址空间的映射关系,进程的某个页面是否在内存中有映射,如果有映射,每个页面具体被映射到哪一个内存页框。每个内存都有自己的页表,每当进程获得CPU的执行权时,寄存器就会装入进程的页表。作为一个简单的实现,虚拟地址映射到物理地址的映射可以概括如下:虚拟地址可以划分为虚拟页号(高位部分)和页内偏移(地位部分)。例如对于16位的地址和4KB大小的页面,高四位可以指定虚拟页面的页号(0~15),而低12位可用于指定页内偏移(0-4095)。页表的目的就是把虚拟页面映射成页框。从数学的角度,页表就是一个函数,函数的输入是虚拟页号,函数的输出是页框号,并据此把虚拟地址转化成物理地址。

  • 页表项的结构

       下面我们将讨论页表项的细节。最总要的项是页框号,毕竟页表的目的就是找到对应的页框号。在/不在标识位是为了表示页面是否被映射到内存中。该标识为0时,表示页面没有被映射,访问该页面将会引起一个页面中断;该标识为1时,表示页面被映射到物理内存,页框号也才有效。保护位用于指出一个页可以进行什么类型的访问。常见的的形式是这个域只有一位,0表示读,1表示读写;更一般的可以三位,分别表示读(r)、写(w)、执行(x)。为了记录页面的使用情况,引入了访问位和修改位。在修改页面时,硬件将自动设置修改位。修改位对于操作系统在将页面换出到磁盘时非常有用,如果该位没有被设置就说明磁盘上的副本任然是有效的,不需要将内存页框中的内容写回到磁盘,可以直接用调入的页面直接覆盖页框中的内容。相反,如果修改位被设置了,那就表示表示该页面是脏页,需要被写回到磁盘。无论是对页面的读还是写操作,操作系统都会该页面被访问时设置访问位,它的值可以帮助操作系统在发生缺页中断是选择换出那个页面显然访问位的值越大表示访问也频繁,显然把访问频率更低的页面换出是更合适的,这一位在页面置换算法中会有很大的作用,更多的关于页面置换相关的算法会在后面有介绍。最后一位是禁用寄存器缓存。对于那些映射到设备寄存器而不是常规的内存页面而言,这个特性是十分重要的。

  • 加速分页过程

    了解完虚拟内存和分页的基础后,有更具体的问题值得我们考虑。在任何分页系统中我们都有考虑如下两个问题:

  1. 虚拟地址到物理地址的映射必须非常快。
  2. 如果虚拟地址空间非常大,页表也十分大

       第一个问题是由于每次访问内存都需要进行虚拟地址到物理地址的转换,很多的指令最终都来自内存,并且还可能会访问内存的操作时,内存中的数据,。因此频繁的访问内存就必须使得虚拟地址到物理地址的转换非常快,以避免映射成为性能的瓶颈。因此介绍两个常用的解决方案。

  • 转换检测缓冲器

  多年来计算机的设计者已经意识到了一个问题,并找到了一个解决方案。这种解决方案的建立建立于一种现象:大多数程序都是对少量的页面进行大量的访问,而不是访问很多页面。因此,只有很少的页表项被重复的读取,而大部分的页表表项很少被访问。我们可以根据这个现象设计出这个解决方案:为计算机设置一个小型的硬件设备,将虚拟地址直接映射到物理地址,而不必在访问页表。这种设备通常称之为转换检测缓冲区(Translation Lookaside Buffer,TLB),有时又称相联存储器(associate memory)或快表如下图所示。它通常在在MMU中,包含少量的表项,每个表项记录一些页面的相关信息,包括虚拟页号、页面的修改位、保护码(读/写/执行)和该页对应的物理页框。除了虚拟页面号,其他各个值和页表中的信息一一对应。

       下面我们看看TLB如何工作。将一个虚拟地址送入到MMU进行地址转换时,硬件首先把地址对应的页面与TLB中的表项同时进行匹配,判断虚拟页面是否在TLB中。如果发现一个有效的匹配并且要进行的访问不违反保护位,则直接将页框号从TLB中取出而不需再访问页表。如果TLB中有匹配的项,但访问操作违反了保护位,则会产生一个保护错误,就像对页表进行非法访问一样。当虚拟页号不在TLB中时会发生什么呢?如果MMU检测到没有合适的匹配项,就会进行正常的页表查询,接着从TLB中淘汰一个表项,用新找到的页表项代替它。这样当这个页面将来再次被访问到时,那么访问TLB就会命中而不需要访问页表。

  • 软件TLB管理

  目前为止我们都假设每一台具有虚拟内存的机器都具有由硬件识别的页表,以及一个TLB。在这种设计中,对TLB的管理以及TLB失效管理都是由MMU硬件来实现。只有在页表中没有找到虚拟页面的映射时才会陷入到操作系统。现在,几乎所有的页面管理都是在软件中实现的。TLB表项被操作系统显式地装载,当发生TLB访问失效时,不再是由MMU到页表中查找并取出需要的页表项,而是将TLB失效问题交给操作系统。操作系统必须先找到该页面,然后从TLB中删除一项,接着装载一个新的表项,最后再执行先前的出错指令。使用软件管理TLB最大的好处是:获得一个非常简单的MMU,这就在CPU芯片上为高速缓存和其他改善性能的设计腾出了相当大的空间并且还需要理解两种不同的TLB失效的区别。当一个页面访问在内存而不在TLB时,称之为软失效(soft miss)。那么此时只需要更新TLB即可,不会产生磁盘I/O。相反当页面不在内存(当然也不在TLB),将产生硬失效,此时则需要现将页面从磁盘中换入到内存中并更新TLB表项。

 

       接下来我们讨论加速分页过程的第二个问题:针对大内存的页表。这个问题来自于现代操作系统使用至少32位的虚拟地址,而且64位也越来越普遍。假设页面的大小为4KB,那么32位的地址空间将会有100万条表项,64位系统就多得不可想象,并且每个进程也还有自己的页表。因此我们接下来就讨论两种方法用于解决巨大的虚拟地址空间。

  • 多级页表

       第一种方法是使用多级页表。在下图中的例子中,32位的虚拟地址被分为10位的PT1域,10位的PT2域和12位的offset域。因为偏移量位12位,因此页面大小为4KB,共有2^20个页面。引入多级页表的原因:避免将全部的页表都一直保持到内存中,特别是那些可能几乎都不会用到的页表就更不应该存在内存中。接下来一起看看下图的二级页表如何工作。在左边是顶级页表,他有1024个表项,对应于10位的PT1域,当一个虚拟地址被送到MMU中,MMU首先提取虚拟地址的PT1域并把它作为访问顶级页表的索引。由索引顶级页表得到的表项中包含二级页表的地址或页框号。然后再根据虚拟地址的PT2域来索引二级页表,并得到虚拟地址所在页面对应的页框号(如果页面被映射了),最后再把虚拟地址的PT3域加到页框号上后送到内存总线上进行内存访问。例如一个32位的地址0X00403004(十进制4206596)位于数据部分12292处,它的虚拟地址对应的PT1=1,PT2=2,offset=4。MMU首先访问顶级页的索引1处,得到二级页表的地址后,再访问二级页表的索引2处得到页面的地址,最后根据页框号访问偏移为4处的内存内容。值得注意的是,上图的虚拟地址空间由超过一百万个页面,但访问一个页面时,只需要顶级页表和一个二级页表即可。

  • 倒排页表

       针对大内存的页表的另一个解决方案是倒排页表(inverted page table),在这种设计中,实际内存中的每个页框对应一个表项,而不是每个虚拟页面对应一个表项。例如64位的虚拟地址空间,4KB的页面大小,4GB的RAM,一个倒排页面仅需要1048576个表项,每个表项记录页框和哪个进程的个虚拟页面对应。在倒排页面的方案中,页表以内存为基准,而不同于多级页表以虚拟页面为基准。

       虽然倒排页面能够节省非常多的内存,但是这种方案存在严重的缺点:从虚拟地址到物理地址的转换会很困难。当进程n​​访问虚拟页面p时,那么就不能使用以p为索引寻找页表,而需要搜索整个页表匹配(n,p)。此外对于每次内存操作都需要对页表完成该搜索,而不仅仅是在发生缺页中断时。通常倒排页表也可以使用对频繁使用的页面进行缓存,当需要访问的页面在TLB中时,访问速度就和使用普通页表相同,但是如果发生TLB失效时,就需要使用软件来搜索整个倒排页表。倒排页表通常只需要维护一个页表,而多级页表通常是每个进程一个页表。

如上是关于虚拟内存的一些介绍。需要指出的是,对于两种解决内存超载的方法(交换技术和虚拟内存),他们之间在本质上存在在不同:交换技术是将整个进程作为换入换出的单位,当内存中有足够的空间时就将整个进程换入到内存中执行,当内存不足,会把进程整个换出到磁盘上。虚拟内存采用的是分页技术,确保进程运行时需要用到的页面必须在内存中被映射即可而不是把所有的页面存放在内存中。虚拟内存能够保证内存中运行更多的进程,当进程发生缺页中断时,可以将CPU交给其他进程执行,因此使用虚拟内存的方式在多道程序设计系统中能保证更多的进程执行。

 

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页