3.2、虚拟内存管理

1、虚拟内存的基本概念

上一节所讨论的各种内存管理策略都是为了同时将多个进程保存在内存中以便允许多道程序设计。他们都具有以下两个共同特征:

1)一次性:作业必须一次性全部装入内存后,方可运行。这会导致两种情况发生:1当作业很大,不能全部被装入内存时,将使该作业无法运行;2当大量作业要求运行时,由于内存不足以容纳所有作业,只能使少数作业先运行,导致系统难以运行多道程序。

2)驻留性:作业被装入内存后,就一直驻留在内存中,其任何部分都不会被换出,直至作业运行结束。运行中的进程,会因等待IO而被阻塞,可能处于长期等待状态。

由上分析可知,许多在程序运行中不用或暂时不用的程序(数据)占据了大量的内存空间,而一些需要运行的作业又无法装入运行,显然浪费了宝贵的内存空间。

要真正理解虚拟内存技术的思想,首先必须了解计算机中著名的局部性原理。著名的Bill Joy说过:“在研究所的时候, 我经常开玩笑的说高速缓存是计算机科学中唯一重要的思想。事实上,高速缓存技术确实极大地影响了计算机系统的设计。”快表、页高速缓存以及虚拟内存技术从广义上讲,都是属于高速缓存技术。这个技术所依赖的原理就是局部性原理。局部性原理既适用于程序结构,也适用于数据结构。

局部性原理表现在以下两个方面:

1)时间局部性。如果程序中的某条指令一旦执行,则不久以后该指令可能再次执行;如果某数据被访问过,则不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。

2)空间局部性。一旦程序访问量某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,其典型情况便是程序的顺序执行。

时间局部性是通过将进来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了“内存-外存”的两级存储器的结构,利用局部性原理实现高速缓存。

基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其与部分留在外存,就可以启动程序执行。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换到外存上,从而腾出空间存放将要调入内存的信息。这样,计算机好像为用户提供了一个比实际内存大的多的存储器,成为虚拟存储器。

之所以将其称为虚拟存储器,是因为这种存储器实际上并不存在,只是由于系统提供了部分装入、请求调入和置换功能后,给用户的感觉是好像存在一个比实际物理内存大得多的存储器。虚拟存储器有以下三个主要特征:

1)多次性,是指无需在作业运行时一次性地全部装入内存,而是允许被分成多次调入内存运行。

2)对换性,是指无需在作业运行时一直常驻内存,而是允许在作业的运行过程中,进行换进和换出。

3)虚拟性 ,是指从逻辑上扩充内存的容量,是用户所看到的内存容量,远大于实际的内存容量。

虚拟内存中,允许讲一个作业分多次调入内存。采用连续分配方式时,会是相当一部分内存空间都处于暂时或永久的空闲状态,造成内存资源的严重浪费,而且也无法从逻辑上扩大内存容量。因此,虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。

虚拟内存的实现有以下三种方式:

l 请求分页存储管理

l 请求分段存储管理

l 请求段页式存储管理

不管哪种方式,都需要有一定的硬件支持。一般需要的支持有以下几个方面:

l 一定容量的内存和外存。

l 页表机制或段表机制,作为主要的数据结构。

l 中断机构,当用户程序要访问的部分尚未调入内存,则产生中断。

l 地址变换机构,逻辑地址到物理地址的变换。

2、请求分页管理方式

请求分页系统建立在基本分页系统基础上,为了支持虚拟存储器功能而增加了请求掉页功能和页面置换功能。请求分也是目前最常用的一种实现虚拟存储器的方法。

在请求分页系统中,只要求将当前需要的一部分页面装入内存,便可以启动作业运行。在作业执行过程中,当所要访问的页面不在内存时,再通过掉页功能将其调入,同时还可以通过置换功能将暂时不用的页面换出到外存上,以便腾出内存空间。

为了实现请求分页,系统必须提供一定的硬件支持。除了需要一定容量的内存及外存的计算机系统,还需要有页表机制、缺页中断机构和地址变换机构。

页表机制不同于基本分页系统,请求分页系统在一作业运行之前不要求全部一次性调入内存,因此在作业的运行过程中,必然会出现要访问的页不在内存的情况,如何发现和处理这种情况是请求分页系统必须解决的两个基本问题。为此,在请求页表项中增加了四个字段:状态位P、访问字段A、修改位M、外存地址。

增加的四个字段说明如下:

状态位P:用于指示该页是否已调入内存,共程序访问时参考。

访问字段A:用于记录本页在一段时间内被访问的次数,或记录本页最近已有多长时间未被访问,供置换算法换出页面时参考。

修改位M:表示该页在调入内存后是否被修改过。

外存地址:用于指出该也在外存上的地址,通常是物理块号,供调入该页时参考。

在请求分页系统中,每当所要访问的页面不在内存时,便产生一个缺页中断,请求操作系统将所缺的页调入内存。缺页中断作为中断同样要经历诸如:保护CPU环境、分析中断原因、转入缺页中断处理程序进行处理、恢复CPU环境等几个步骤。但与一般的中断相比,它有以下两个明显的区别:

在指令执行期间产生和处理中断信号,而非一条指令执行完后。

一条指令在执行期间,可能产生多次缺页中断。

请求分页系统中的地址变换机构,是在分页系统地址变换机构的基础上,为实现虚拟内存,又增加了某些功能而形成的。

在进行地质变换时,先检索快表:

若找到要访问的页,边修改页表中的访问位,然后利用页表项中给出的物理块号和页内地址形成物理地址。

若为找到该页的页表项,应到内存中去查找页表,在对比页表项中的状态位P,看该页是否已调入内存,未调入则产生缺页中断,请求从外存把该页调入内存。

3、页面置换算法

进程运行时,若其访问的页面不在内存而需将其调入,但内存已无空闲空间时,就需要从内存中调出一页程序或数据,送入磁盘的兑换区。而选择调出页面的算法就成为页面置换算法。好的页面置换算法应有较低的页面更换频率,也就是说,应将以后不会在访问或者以后较长时间内不会访问的页面先调出。

常见的置换算法有以下四种:

最佳置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若干页面中那个是未来最长时间内不再被访问的,因而该算法无法实现。

优先淘汰最早进入内存的页面,亦即在内存中驻留时间最久的页面。该算法实现简单,只需把调入内存的页面根据先后次序连接成队列,设置一个指针总指向最先的页面。但该算法与进程实际运行时的规律不适应,因为在进程中,有的页面经常被访问。

选择最近最长时间未访问过的页面予以淘汰,他认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。

LRU性能较好,但需要寄存器和栈的硬件支持。LRU是堆栈类的算法。理论上可以证明,堆栈类算法不可能出现Belady异常。FIFO算法基于队列实现,不是堆栈类算法。

LRU算法的性能接近于OPT,但实现起来比较困难,且开销大;FIFP算法实现简单,但性能差。所以操作系统的设计者尝试了很多算法,试图用比较小的开销接近LRU的性能,这类算法都是CLOCK算法的变体。

简单的CLOCK算法是给每一帧关联一个附加位,称为使用位。当某一页首次装入主存时,该帧的使用位设置为1;当该页随后再被访问到时,他的使用位也被置为1.对于液体换算法,用于替换的候选帧集合看做一个循环缓冲区,并且有一个指针与之相关联。当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。当需要替换一页时,操作系统扫描缓冲区,以查找使用位被指为0的帧,每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0;如果在这个过程开始时,缓冲区中所有帧的使用位均为0,则选择遇到的第一个帧替换;如果所有帧的使用位均为1,则指针在缓冲区中完整的循环一周,把所有使用位都置为0,并且停留在最初的位置上,替换该帧中的页。由于该算法循环的检查各页面的情况,故称为CLOCK算法,又称为最近未用NRU( Not recently used)算法。

CLOCK算法的性能比较接近LRU,而通过增加使用的位数目,可是使得CLOCK算法更加高效。在使用位的基础上再增加一个修改位,则得到改进型的CLOCK置换算法。这样,每一帧都出于以下四种情况之一。

1)最近未被访问,也未被修改(u=0,m=0)。

2)最近被访问,但未被修改(u=1,m=0)。

3)最近未被访问,但被修改(u=0,m=1)。

4)最近被访问,被修改(u=1,m=1)。

算法执行如下操作步骤:

1)从指针的当前位置开始,扫描帧缓冲区。在这次扫描过程中,对使用位不作任何修改,选择遇到的第一个帧(u=0,m=0)用于替换。

2)如果第1步失败,则重新扫描,查找(u=0,m=1)的帧。选额遇到的第一个这样的帧用于替换。在这个扫面过程中,对每个跳过的帧,把它的使用位设置成0.

3)如果第2步失败,指针将回到它的最初位置,并且集合中所有帧的使用位均为0.重复第一步,并且如果有必要重复第2步。这样将可以找到供替换的帧。

改进型的CLOCK算法优于简单的CLOCK算法之处在于替换时首选没有变化的页。由于修改过的页在被替换之前必须写回,因而这样做会节省时间。

4、页面分配策略

对于分页式的虚拟内存,在准备执行时,不需要也不可能把一个进程的所有页都读取到主存,因此,操作系统必须决定读取多少页。也就是说,给特定的进程分配多大的主存空间。这需要考虑以下几点:

1)分配给一个进程的存储量越小,在任何时候驻留在主存的进程数越多,从而可以提高处理器的时间利用率。

2)如果一个进程在主存中的页数过少,尽管有局部性原理,页错误率仍然会相对较高。

3)如果页数过多,由于局部性原理,给特定的进程分配更多的主存空间对该进程的错误率没有明显的影响。

基于这些因素,现代操作系统通常采用三种策略:

1)固定分配局部置换。它为每个进程分配一定数量的物理块,在整个运行期间都不改变。若进程在运行中发现缺页,则只能从该进程在内存的页面中选出一个换出,然后再调入需要的页面。实现这种策略难以确定为每个进程应分配的物理块数量:太少会频繁出现缺页中断,太多又会使CPU和其他资源利用率下降。

2)可变分配全局置换。这是最易于实现的物理块分配和置换策略,为系统中的每个进程分配一定数量的物理块,操作系统自身也保持一个空闲物理块队列。当某进程发现缺页时,系统从空闲物理块队列中取出物理块分配给该进程,并将于调入的页装入其中。

3)可变分配局部置换。它为每个进程分配一定数目的物理块,当某进程发现缺页时,只允许从该进程在内存的页面中选出一页换出,这样就不会影响其他进程的运行。如果进程在运行中频繁的换页,系统需再为该进程分配若干附加物理块,直至该进程缺页率趋于适当程度为止;反之,若一个进程在运行过程中缺页率特别低,则此时可适当减少该进程的物理块。

为确定系统将进程运行时所缺的页面调入内存的时机,可采取预调页策略或请求调页策略。

1)预调页策略。根据局部性原理,一次调入若干个相邻的页可能比一次调入一页更高效。但如果调入的一批页面中大厦多数都未被访问,则又是低效的。所以就需要采用以预测为基础的预调页策略,将预计在不久之后便会被访问的页面预先调入内存。但目前预调页的成功率仅约50%。股这种策略主要用于进程的首次调入时,有程序员指出应该先调入哪些页。

2)请求调页策略。进程在运行中需要访问的页面不在内存而提出的请求,由系统将所需页面调入内存。这种策略调入的页一定会被访问,且这种策略比较易于实现,故在目前的虚拟存储器中大多采用此策略。它的缺点在于每次调入一页,会花费过多的IO开销。

3)从何处调入页面。

请求分页系统中的外存分为两部分:用于存放文件的文件区和用于存放对换页面的对换区。对换区通常是采用连续分配方式,而文件区采用离散分配方式,故对换区的磁盘IO速度比文件区高。这里从何处调入页面有三种情况:

1)系统拥有足够的对换区空间:可以全部从对换区调入所需页面,以提高调页速度。为此,在进程运行前,需将与该进程有关的文件从文件区复制到对换区。

2)系统缺少足够的对换区空间:饭不会被修改的文件都直接从文件区调入;而当换出这些页面时,由于他们未被修改而不必再将它们换出。但对于那些可能被修改的部分,在将他们换出时需调到对换区,以后需要以后需要时再从对换区调入。

3)UNIX方式:与进程有关的文件都存放在文件去,故未运行过的页面都应从文件区调入。曾经运行过的但有被换出的页面,由于是被放在对换区,因此下次调入时应从对换区调入。进程请求的共享页面若被其他进程调入内粗你,则无需再从对换区调入。

5、抖动和工作集

在进程的页面置换过程中,频繁的页面调度行为成为抖动,或颠簸。如果一个进程在换页上用的时间多于执行时间,那么这个进程就在颠簸。

使用虚拟内存技术,操作系统中进程通常只有一部分块位于主存中,从而可以在内存中保留更多的进程以提高系统效率。此外,由于未用到的块不需要换入换出内存,因为节省了时间。但是系统必须很“聪明”地管理页面分配方案。在稳定状态,几乎主存的所有空间都被禁成块占据,处理器和操作系统可以直接访问到尽可能多的进程。但如果管理不当,系统发生抖动现象,处理器的大部分时间都将用于交换快,及请求调入页面的操作,而不是执行进程的指令,这就会大大降低系统效率。前面讲解的页面置换算法就是是讨论这里的分配方案,尽量避免抖动现象。

另外,为了防止出现抖动现象,需要选择合适的驻留集大小。驻留集(或工作集)是指在某段时间间隔内,进程要访问的页面集合。经常被使用的页面需要在驻留集中,而长期不被使用的页面要从驻留集中被丢弃。驻留集模型使用较为简单:操作系统跟踪每个进程的驻留集,并为进程分配大于驻留集的的空间。如果还有空闲,那么可启动另一个进程。如果所有驻留集之和增加一直超过了可用物理块啊的总数,那么系统会怎听一个进程,将其页面调出并且将其物理块分配给其他进程。

正确选择驻留集的大小,对存储器的有效利用和系统吞吐量的提高,都将产生重要的影响。

6、请求分段管理方式

请求分段存储管理系统已基本短时存储管理为基础,为用户提供一个比主存容量更大的虚拟存储器。作业的若干分段别放入内存,就可以开始作业运行,作业的其他部分被放在外存中,等到需要的时候才被调入内存。请求分段管理方式与请求分页存储管理方式类似,支不果断的大小不是固定的,在内存中空闲区域不够时,不能像分页一样采用简单的置换算法,可能需要置换多个端才有足够的空闲区。

1)段表机制。

在请求分段系统中,作业运行之前,只要求将当前需要的若干个分段装入内存,便可启动作业运行。在作业运行过程中,若要访问的分段不在内存,则通过缺段中断处理程序将其调入,同时还可以通过置换功能将暂时不用的分段调出到外存上,以便腾出与内存空间。为此,应对段表进程扩充。

在扩充后的段表项中,除了段号、段长、段在内存中的基址外,还增加了以下字段:

l 存取方式:表示本段的存取属性(只执行、只读或读写)。

l 访问字段A:记录该段被访问的频繁程度。

l 修改位M:表示该段进入主存后,是否已被修改,供置换段时参考。

l 存在位P:只是本段是否已调入主存,供程序访问时参考。

l 增补位:这是请求分段系统所特有的字段,表示本段在运行过程中是否有动态增长。

l 外存始址:只是本段在外村中的起始地址,即起始盘块号。

2)缺段中断机构

在请求分段系统中,当所要访问的段上未调入主存时,便由缺段中断机构产生一个缺段中断信号,请求操作系统将所要访问的段调入主存。

3)地址变换机构

请求分段系统中的地址变换机构在基本分段系统的地址变换机构的基础上,增加缺段中断请求及处理等形成。请求分段系统时以段为单位进行主存空间的分配,整段信息的装入、调出,有时需要主存空间的移动,这些都增加了系统的开销。

1)分段的共享

通过共享段来实现,每一个表项都是共享段的信息,记录了共享此段的每个进程情况。

2)分段的保护

越界检查:短号超过段表长度或段内偏移超过段长时做越界中断处理。

存取控制检查:段表项中存取控制字段规定了对该段的访问方式,比如只读、读写等。

环保护机构:一个程序可以访问驻留在相同环或较高特权环中的数据;一个程序可以调用驻留在相同环或较高特权环中的服务。

7、请求段页式管理方式

请求段页式管理方式只要求将作业若干页或段装入内存就可以开始运行作业,作业的其他部分别放在外存中,等待运行需要的时候才被调入内存,

请求段页式管理方式要求相对程序按逻辑意义分段后再分页,所以相对于请求页式管理方式能够方便用户使用,便于共享、保护和动态链接。进程在启动的时候采取与装入模式,则可以根据段的意义装入某些进程运行开始阶段所需要的段。