虚存管理

 虚拟存储器由主存储器和联机工作的外部存储器共同组成。在目前的计算机系统中,主存储器通常用动态随机存储器(DRAM)实现,它的存储容量相对比较小,速度比较快,单位容量的价格比较贵。联机工作的外部存储器通常为磁盘存储器,它的存储容量很大,与主存储器相比,速度很低,单位容量的价格很便宜。这两个存储器在硬件和系统软件的共同管理下,对于应用程序员,可以把它们看来是一个单一的存储器,是一个存储容量非常大的主存储器。
  虚拟存储器又称虚拟存储系统,或虚拟存储体系等,其概念是1961年英国曼彻斯特大学的Kilbrn等人提出的。到了70年代被广泛地应用于大中型计算机系统中。目前,许多小型计算机,甚至微型机也开始使用虚拟存储器。
  以下首先介绍虚拟存储器的基本工作原理,然后介绍虚拟存储器的地址映象和变换方法,页面替换算法,加快地址变换速度的方法等,最后分析影响虚拟存储器性能的几个主要问题。本书主要从计算机系统结构的角度来讲述虚拟存储器,这与《计算机操作系统》课中从"存储器管理"这一角度来介绍虚拟存储器是很不一样。可以说,只有当你学习了《计算机系统结构》之后,才能真正懂得虚拟存储器的原理和实现方法。

3.2.1 虚拟存储器工作原理

页式虚拟存储器是虚拟存储器中用得比较广泛的一种,另外的段式虚拟存储器和段页式虚拟存储器主要是因为地址变换方法不同产生的。本节首先以页式虚拟存储器为例介绍虚拟存储器的工作原理。然后再具体介绍页式、段式和段页式三种虚拟存储器的地址变换方式及外部地址变换方式。
  在页式虚拟存储器中,主存储器的页称为实页,虚拟存储器中的页称为虚页。下面给出了主存地址和虚拟地址的组成,如图3.16所示。

页式虚拟存储器:把主存储器、磁盘存储器和虚拟存储器都划分成固定大小的块--页(Page),就象一本书由很多页组成,每一页中容纳的字数是相同的。

图3.16 虚拟存储器中的地址组成

一个主存地址A由两部分组成,实页号p和页内偏移d,如图3.16(a)所示。一个虚地址Av由三部分组成,用户号U、虚页号P和页内偏移D,如图3.16(b)所示。

一个用户程序要访问虚拟存储器时,必须给出多用户虚拟地址Av。在操作系统和有关硬件的共同管理下,首先进行内部地址变换。如果变换成功(命中),得到主存实页号p。而多用户虚拟地址中的页内偏移D可以直接作为主存实地址中的页内偏移d,这样,只要把主存实页号p与它的页内偏移d直接拼接起来就得到主存实地址A。于是,就可以用这个主存实地址A去访问主存储器,得到所需要的数据。页式虚拟存储器的工作过程如图3.17所示。

图3.17 页式虚拟存储器工作原理

  如果内部地址变换失败(未命中),表示要访问数据不在主存储器中,必须访问磁盘存储器。这时,要进行外部地址变换。外部地址变换主要用软件实现,首先查外页表得到与虚页号P相对应的磁盘存储器的实地址,然后再查内页表(主存实页表),看主存储器中是否有空页。如果主存储器中还有空页,只要找到空页号。把磁盘存储器的实地址和主存储器的实页号送入输入输出处理机(输入输出通道)等,在输入输出处理机的控制下,把要访问数据所在的一整页都从磁盘存储器调入到主存储器。
  如果主存储器中已经没有空页,则要采用某种页面替换算法,先把主存中暂时不用的一页写回到磁盘存储器中原来的位置上,以便腾出空位置来存放新的页。当然,如果要替换出去的那一页从它调入主存储器之后从来没有被修该过,就不需要把它送回磁盘存储器,直接用调入的新页把它覆盖掉即可。
  在进行外部地址变换时,如果没有命中,则表示所需要的页还不在磁盘存储器中。这时,要在操作系统控制下,启动磁带机、光盘存储器等海量存储器,先把与所需要数据相关的文件从海量存储器中调入磁盘存储器。

3.2.2 地址的映象与变换

在虚拟存储器中有三种地址空间,一种是虚拟地址空间,第二种是主存储器的地址空间,第三个是辅存地址空间,与这三种地址空间相对应,有三种地址,即虚拟地址、主存地址和磁盘存储器地址。
根据所采用的地址映象和地址变换方法不同,有多种不同类型的虚拟存储器。目前主要有页式虚拟存储器、段式虚拟存储器和段页式虚拟存储器等三种。然而,这三种虚拟存储器的基本原理、所采用的主要技术和工作流程等是基本相同的。

虚拟地址空间:又称虚存空间或虚拟存储器空间,它是应用程序员用来编写程序的地址空间,这个地址空间非常大。
主存储器的地址空间:也称主存地址空间、主存物理空间或实存地址空间
辅存地址空间:也就是磁盘存储器的地址空间
虚拟地址:虚存空间上的地址,又称为虚存地址或者虚地址
主存地址:又称为主存实地址、主存物理地址、主存储器地址
磁盘存储器地址:又称为磁盘地址、辅存地址
地址映像:把虚拟地址空间映象到主存地址空间,具体地说,就是把用户用虚拟地址编写的程序按照某种规则装入到主存储器中,并建立多用户虚地址与主存实地址之间的对应关系。
地址变换:在程序被装入主存储器之后,在实际运行时,把多用户虚地址变换成主存实地址(内部地址变换)或磁盘存储器地址(外部地址变换)。

3.2.2.1 段式虚拟存储器

程序员在编写程序时,一般按照程序的内容和函数关系把程序分成段,每段都有自己的名字,并且希望能够按照名称或序号来访问程序段。采用段式虚拟存储器能够比较好地满足程序员的这些要求。
程序段可以是主程序,也可以是各种子程序,还可以是数据块、数组、表格、向量等。每个程序段都从0地址开始编址,其长度可长可短,甚至可以在程序执行时动态地改变程序段的长度。
每一道程序(或一个用户、一个进程等)由一张段表控制,每个程序段在段表中占一行。段表的内容主要包括段号(或段名)、该程序段的长度、在主存中的起始地址等三个字段。 如果第一个字段用段号而不用段名表示,而且段号是连续的,则这一个字段可以省掉。只要根据段表中的另外两个字段:该程序段在主存中的起始地址和段的长度,就可以把该程序段唯一地映象到主存储器的确定位置中。
另外,根据需要还可以在段表中增加其它信息,如指出该程序段的访问方式(可读可写、只读、某些用户可写、只能执行等)、是否已经装入主存的标志、该段程序是否被修改过的标志等,不过,增加的这些字段在地址映象过程中是用不上的,可以用于地址变换过程中。

图3.18是段式虚拟存储器的地址映象方法

一个由4个程序段组成的程序,在一张段表的控制下,把这4段程序分别映象到主存储器的各个不同区域中。每一个程序段可以映象到主存储器的任意位置,可以连续存放,也可以不连续存放,可以顺序存放,也可以前后到置,放置比较自由。

使用地址映象方法,可以把在虚拟地址空间中编写的程序装入到了主存储器中。但在在程序实际执行时,还要把用来访问主存储器的多用户虚地址变换成主存实地址,有了主存实地址才能访问已经装在主存储器中用户程序或数据,地址变换过程如图3.19所示。在CPU内通常有一个段表基址寄存器堆,每道程序使用其中的一个基址寄存器。因此,可以通过多用户虚地址中的用户号直接找到与这道程序相对应的基址寄存器。通常,段表是放在主存储器中的,从这个基址寄存器中可以直接读出段表的起始地址,把这个起始地址与多用户虚地址中段号相加就能得到这个程序段的段表地址。访问这个段表地址,就能得到有关该程序段的全部信息。如果装入位给出的信息表示要访问的这个程序段已经在主存储器中,这时,只要把段表中给出的该程序段的起始地址与多用户虚地址中的段内偏移D相加就能得到主存实地址。

一个多用户虚地址由三部分组成,用户号U(或程序号)、段号S和段内偏移D
段表中的段长和访问方式是用来保护程序段的。可以根据程序段的起始地址和段长计算出本次访问主存储器的地址是否越界。访问方式可以指出本程序段是否需要保护和保护的级别。
例如,对于子程序段,通常只能执行,不能改写;对于一些常数段或数据库中的数据段,一般用户程序只能读,不能改写,不能执行;对于有些需要保密的表格等,一般用户应禁止访问等。
如果装入位给出的信息表示要访问的这个程序段不在主存储器中,则段表中的起始地址和访问方式字段等均无用。这时,可以把它们用来存放该程序段在磁盘存储器中的起始地址等信息。使用磁盘存储器的起始地址和段长就可以从磁盘存储器中把该程序段读到主存储器中来。
根据需要还可以在段表中增加其它字段。
例如,增加一个修改标志字段,表示本程序段是否被修改过,如果这个程序段从装入主存储器起一直没有被修改过,则在需要把它替换出主存储器时,不必把它写回到外部的磁盘存储器中,只要用新调入的程序段把它覆盖掉即可。如果这个程序段被修改过,则必须先把这个程序段全部写回到磁盘存储器中存放这个程序段的原来位置上。
段表本身也是一个段,一般常驻在主存储器中。如果段表太长,也可以把暂时不用的一部分段表放在磁盘存储器中,当需要时再把有用的段表调入主存储器。

段式虚拟存储器的主要优点如下:
1程序的模块化性能好。
对于大程序,可以划分成多个程序段,每个程序段赋予不同的名字,由多个程序员并行编写,分别编译和调试。由于各个程序段在功能上是相互独立的,因此,一个程序段的修改和增删等不会影响其它程序段,从而可以缩短程序的编制和调试所用的时间。
2便于程序和数据的共享。
由于程序段是按功能来划分的,如子程序段、数组段、表格段等,每个程序段有比较完整的功能,因此,被共享的可能性很大。当某个程序段需要共享时,只要在主存储器中装入一份,同时在需要调用这个程序段的那些程序(或用户)的段表中都使用这个程序段的主存起始地址和段长等信息,这样,就能很方便地实现程序段的共享。
3程序的动态链接和调度比较容易。
由于每一个程序段都是一组有独立意义的数据块或具有完整功能的程序段,因此,在程序运行过程中,可以根据需要一次就把一个程序段或数据块都装入到主存储器中,并且可以在装入时才实行动态链接。
4便于实现信息保护。在一般情况下,一段程序是否需要保护是根据这个段程序的功能来决定的。
例如,一般的数据段能读能写,常数段只能读不能写,所有的数据段都不允许执行,而子程序一般只能执行不能修改等。由于段式虚拟存储器本身就是按照功能划分程序段的,因此,只要在段表中设置一个信息保护字段,就能根据需要很方便地实现对该程序段的保护。
段式虚拟存储器的主要缺点是:
1地址变换所花费的时间比较长。从图3.19中看到,从多用户虚地址变换到主存实地址需要查两次表,做两次加法运算。
第一次是查找段表的基地址表,第二次是查找段表。两次加法第一次是将段号和段表基址相加得到此段号在段表中的位置,第二次是将段内偏移和段在内层中的基址相加得到主存地址
2主存储器的利用率往往比较低。
由于每个程序段的长度是不同的,一个程序段通常要装在一个连续的主存空间中,程序段在主存储器中不断地调入调出,有些程序段在执行过程中还要动态增加长度,从而使得主存储器中有很多的空隙存在。当然,也可以采用一些好的算法来减少空隙的数量,或者通过定时运行回收程序来合并这些空隙,但无疑增加了系统的开销。
3对辅存(磁盘存储器)的管理比较困难。磁盘存储器通常是按固定大小的块来访问的,如何把不定长度的程序段映象到固定长度的磁盘存储器中,需要做一次地址变换。

3.2.2.2 页式虚拟存储器

在段式虚拟存储器中,虚拟地址空间按段划分,段的长度根据程序的逻辑需要可以是任意长的,主存储器的地址空间仍保持一维连续线性空间不划分。因此,段表中主存地址和段长度两个字段的长度都很长。这样,既增加了硬件开销,又降低了地址映象和变换的速度。
页式虚拟存储器把虚拟地址空间划分成一个个固定大小的块,每块称为一页(Page),把主存储器的地址空间也按虚拟地址空间同样的大小划分为页。页是一种逻辑上的划分,它可以由系统管理软件任意指定。然而,由于磁盘存储器的物理块大小是0.5KB,为了与外部存储器,特别是磁盘存储器相配合,因此,虚拟存储器中页的大小通常指定为0.5KB的整倍数。目前一般计算机系统中,一页的大小通常为1KB至16KB。
在虚拟存储外中,虚拟地址空间中的页称为虚页,主存地址空间的页称为实页。这样,把一个在虚拟地址空间中编写的用户程序映象到主存实地址空间时,只需要建立从虚页号到实页号的地址变换即可,从而可以大大缩短地址的长度,既节省了硬件,又能加快地址变换的速度。
页式虚拟存储器的地址映象方法如图3.20所示。

图3.20 页式虚拟存储器的地址映象

一个在虚拟地址空间中编写的用户程序共有3页,其中最后一页没有满,要浪费掉一部分。页表共有4行,每行与用户程序的一页对应。如果页表中第一个字段给出的页号是连续的,则这一个字段可以省掉。只要根据页表中的主存页号就能把该用户程序的每一页唯一地映象到主存储器的确定位置中,用户程序的每一页都可以映象到主存储器的任意一页的位置上,页与页之间可以连续映象,也可以间断映象,可以按顺序来映象,也可以前后到置等。
与段式虚拟存储器相比,由于每一页的长度是固定的,因此,不需要象段式虚拟存储器中的段长度这一字段,另外,主存地址这一字段只需要指出主存储器的页号,与段式虚拟存储器中的主存地址必须指出整个主存地址长度相比要节省很多。

地址映象把用户用虚拟地址编写的程序装入到了主存实地址空间中,但在程序运行过程中,还必须把用户程序中的虚拟地址变换成主存实地址,这是地址变换要做的工作。页式虚拟存储器的地址变换过程如图3.21所示。
一个多用户虚地址由三部分组成:用户号U、虚页号P和页内偏移D
在CPU内部有一个基址寄存器堆,用来存放页表的基地址,每个用户(每道程序)使用其中的一个基址寄存器。通过多用户虚地址中的用户号U可以直接找到与这个用户程序相对应的基址寄存器,从这个基址寄存器中读出页表起始地址。这个用户程序的每一页在页表中都有对应的一行。由于页表是按照用户程序的页号顺序存放,因此,可以省掉页号这一字段。要找到被访问页的页表地址,只要把页表起始地址与多用户虚地址中虚页号相加即可。访问这个页表地址,就能得到被访问页的所有信息。把得到的主存页号p与多用户虚地址中的页内偏移D直接拼接起来得到主存实地址A。
页表中的其它信息,如装入位、修改位、和各种标志信息等的含义和用法与段式虚拟存储器基本相同。

图3.21 页式虚拟存储器的地址变换

一个多用户虚地址由三部分组成:用户号U、虚页号P和页内偏移D

页式虚拟存储器的主要优点是:
1 主存储器的利用率比较高。
每个用户程序只有不到一页(平均为半页)的浪费,与段式虚拟存储器每两个程序段之间都有浪费相比要节省许多。
2 页表相对比较简单。
它需要保存的字段数比较少,一些关键字段的长度要短许多多,因此,节省了页表的存储容量。
3 地址映象和变换的速度比较快。
在把用户程序装入到主存储器的过程中,只要建立用户程序的虚页号与主存储器的实页号之间的对应关系即可,不必使用整个主存的地址长度,也不必考虑每页的长度等。在地址变换过程中,从图3.21中可以看到,主存实地址A是由页表中的主存页号p和多用户虚地址中的页内偏移D直接拼接而得到的,不必经过任何运算,因此,地址变换的速度比较快。
4 对辅存(磁盘存储器)的管理比较容易。
因为页的大小一般取磁盘存储器物理块大小(512字节)的整倍数。
页式虚拟存储器的主要缺点有两个:
1 程序的模块化性能不好。
由于用户程序是强制按照固定大小的页来划分的,而程序段的实际长度一般是不固定的。因此,页式虚拟存储器中一页通常不能表示一个完整的程序功能。一页可能只是一个程序段中的一部分,也可能在一页中包含了两个或两个以上程序段。
2 页表很长,需要占用很大的存储空间。
通常,虚拟存储器中每一页,在页表中都要占有一个存储字。假设有一个页式虚拟存储器,它的虚拟存储空间大小为4GB,每一页的大小为1KB,则页表的容量为4M存储字。如果每个页表存储字占用4个字节,则页表的存储容量为16MB。

3.2.2.3 段页式虚拟存储器

段页式虚拟存储器:其基本思想是对用户原来编写程序的虚拟存储空间采用分段的方法管理,而对主存储器的物理空间采用分页的方法管理。
段页式虚拟存储器一方面具有段式虚拟存储器的主要优点。
例如,用户程序可以模块化编写,程序段的共享和信息的保护都比较方便,程序可以在执行时再动态链接等。
另一方面也具有页式虚拟存储器的主要优点。
例如,主存储器的利用率比较高,对辅助存储器(磁盘存储器)的管理比较容易等。
在段页式虚拟存储器中,用户仍然按照逻辑的程序段来编写程序,但每一个程序段又被分成若干个固定大小的页。如图3.22所示,一个用户程序由三个独立的程序段组成。0号程序段的长度为12KB,由于页的长度是4KB,因此,正好分成3页。1号程序段的长度为10KB,也分成3页,其中最后一页有2KB是浪费的。2号程序段的长度为5KB,分成2页,其中后面一页浪费3KB。

图3.22 段页式虚拟存储器的地址映象

一个用户程序的三个程序段通过一张段表来控制,与段式虚拟存储器一样,每个程序段在段表中占一行。在段表中给出该程序段的页表长度和页表的起始地址,根据这两个参数就能找到这个程序段的页表。页表的长度就是这个程序段的页数,页表中给出这个程序段的每一页在主存储器中的实页号。这样就完成了用户程序的虚拟空间到主存实地址空间的映象。

在虚拟存储空间到主存物理空间的映象过程中,是以页为单位进行的。与段式虚拟存储器不同的地方是:用户程序中每一页不能被映象到主存储器的任意开始位置上,只能映象到主存储器的一个整页的位置中。这种映象方法可以缩短页表的存储容量,加快地址映象和变换的速度,因为主存实地址只需要把页表中的实页号和虚拟地址中的页内偏移拼接起来即可,不必进行任何运算。
在段页式虚拟存储器中,一个多用户虚地址由四部分组成:用户号U、段号S、虚页号P和页内偏移D,如图3.23所示。
在程序运行过程中,要把用户程序中的虚拟地址变换成主存实地址,必须分两步来进行。先查段表,得到该程序段的页表起始地址和页表长度,再通过查页表找到要访问的主存实页号,最后把实页号p与页内偏移d拼接得到主存的实地址。地址变换的具体过程可以参照段式虚拟存储器和页式虚拟存储器,以及图3.23自己弄清楚,这里不再详细叙述。
在许多大中型计算机中,如IBM370/168、Multics、Amdahl470V/6等都采用这种段页式虚拟存储器。

图3.23 段页式虚拟存储器的地址变换

从图3.23中看到,在段页式虚拟存储器中,要从主存储器中访问一个数据(取指令、读操作数或写结果),需要查两次表,一次是页表,另一次是段表。如果段表和页表都是在主存储器中的,则要访问主存储器三次。对于段式虚拟存储器和页式虚拟存储器也要访问主存储器两次。因此,要想使虚拟存储器的速度接近主存储器的速度。或者说,要想使虚拟存储器能够真正实用,必须加快查表的速度,有关具体方法,将在下一节中作比较详细的介绍。

3.2.2.4 外部地址变换

以上介绍了内部地址映象和变换方法,即把虚拟地址空间映象到主存物理地址空间,以及把虚拟地址变换成主存实地址。当页表或段表中的有效位指示发生页面失效时,表示需要访问的那一页或那一个程序段还没有装入到主存储器中,这时必须进行外部地址变换。

外部地址变换的目的是要找到辅存(磁盘存储器)的实地址,并且把需要访问的那一页或那一个程序段调入到主存储器中。
在操作系统中,通常把页面失效当作一种异常故障来处理。在页式虚拟存储器和段页式虚拟存储器中,由于页面是按固定大小划分的,因此,页面失效可能发生在一条指令的执行过程中。
例如,对于按照字节编址的主存储器,有可能出现一条指令跨越页面存放的情况。如果前一页已经在主存储器中,而后一页还没有调入主存储器,则在取指令过程中就可能发生页面失效。
另外,在取操作数,特别是取字符串,间接寻址及写回运算结果的过程中都可能发生页面失效。对于这种异常故障,处理机必须立即响应并且处理,否则正在执行的指令无法继续执行下去。
如何保存和恢复故障点的现场,使得在故障处理完成之后能正确返回到断点处继续执行程序,这是保证虚拟存储器能否正常工作的一个关键问题。这与一般的子程序调用或程序中断等情况不同,因为在正常情况下都是当一条指令执行完成后才转入子程序或中断处理程序的。有三种方法可解决这个问题。一种方法是采用硬件的缓冲寄存器,把执行该指令时的故障现场全部保存到缓冲寄存器中,等页面失效处理完成之后,可以完整地恢复现场,从故障点继续执行还没有执行完的指令。第二种方法是只保存部分现场,如程序计数器和处理机状态字等,等页面失效处理完后,从头开始执行还没有执行完成的指令。第三种方法是采用指令预判技术,对于那些有可能发生跨页执行的指令,如字符串指令等。在指令执行之前,就作页面失效处理,等执行这条指令所需要的所有页面全部调入主存储器之后,才开始执行该条指令。

磁盘存储器的地址格式如图3.24所示。由于在一台机器种可能有多个磁盘,因此,首先要给出磁盘号,然后是一个磁盘内的拄面号、磁头号和块号。磁盘存储器每一个物理块的大小是512字节。由于在页式虚拟存储器和段页式虚拟存储器中,每一页面的大小是固定的,通常是磁盘存储器物理块大小的整数倍,这个倍数在外部地址变换软件中是知道的。对于段式虚拟存储器,在段表中有一个字段是段长度,根据段长度和磁盘存储器物理块的大小就能很容易计算出本次页面失效需要调入的磁盘存储器的物理块数。因此,在进行外部地址变换时只要给出磁盘存储器的起始地址,就能把一整页或一整个程序段都调入主存储器中。
虚拟地址空间中的每一个页面或每一个程序段,在外页表中都有对应的一个存储字。在每一个存储字中除了必须有磁盘存储器的地址之外,至少还应该包括一个装入位。

外页表:因为它是在外部地址变换中使用的,与在内部地址变换中使用的页表被称为内页表相对应。

图3.25 外部地址变换

由于每一个用户程序有一张外页表,因此,通过多用户虚地址中的用户号U就能够找到该用户程序的外页表起始地址。再通过虚页号P就能唯一确定外页表中与需要访问的页面相对应的那个存储字。
如果该存储字中的装入位为"1",则表示要访问的页面已经在磁盘存储器中,否则表示要访问的页面还不在磁盘存储器中,需要从磁带、光盘存储器等海量存储器中调入。
在段式虚拟存储器中,采用类似的方法,通过段号S唯一确定外页表中与需要访问的程序段相对应的那个存储字。
由于虚拟地址空间中的每一个页面(每一个程序段),在外页表中都要有对应的一个存储字。因此,外页表也要采用前面介绍的多级页表技术。
如果在整个程序的装入和执行过程中,不需要磁带、光盘等海量存储器介入,则外页表页可以与内页表合并成一个页表。当装入位为"1"时,表示要访问的页面已经在主存储器中,这时,页表中的地址字段指出该页在主存储器中的实页号。如果装入位为"0",则表示要访问的页面在磁盘存储器中,这时,页表中的地址字段指出该页存放在磁盘存储器中的磁盘实地址。

3.2.3 加快内部地址变换的方法

在虚拟存储器中,如果不采取有效的措施,访问主存储器的速度将要降低几倍。这不符合存储系统的要求,因为在存储系统中,要求系统的访问速度接近于速度最高的那个存储器。造成虚拟存储器速度降低的主要原因有如下两个:
1在段式或页式虚拟存储器中,要访问主存储器必须先查段表或页表,在段页式虚拟存储器中,既要查段表也要查页表。如果段表和页表都在主存储器中,那么,包括访问主存储器本身这一次在内,主存储器的访问速度将要降低2至3倍。
2当页表和段表的容量超过了一个页面的大小时,它们就有可能被映象到主存储器的不连续的页面位置上。这样,按照地址查找主存实页号的方法,即把页表起始地址与多用户虚地址中虚页号相加,就不能成立。
  目前的解决办法是采用多级页表。

多级页表:首先由页表基地址寄存器指出第一级页表的基地址,再用第一级页表各单元中的地址字段指出第二级页表的基地址,如此下去,构成一个树型结构的多级页表。在最后一级页表中给出主存实页号等信息。
如果虚拟存储空间的大小为Nv,页面的大小为Np,一个页表存储字的大小为Nd,则页表的级数g可以通过下面的公式计算:
如果一个页式虚拟存储器的虚存储空间大小Nv=4GB,每一页的大小Np=1KB,每个页表存储字占用4个字节,即
Nd=4B。以字节为单位,把这些数据代入上面的公式,计算得到页表的级数:

必须采用3级页表。
第1级页表为1页,存储容量1KB,可以有256个存储字(每个存储字占用4个字节),在这里只需要用其中的64个存储字。用第一级页表的64个存储字指向第二级页表的64个页面,每个页面各有256个存储字,这样,二级页表共有16K个存储字。同样,第三级页表共有16K个页面,即4M个存储字。这4M个存储字正好用来存放虚拟存储空间的4M个页面信息。因此,第1级页表为1个页面,第2级页表有64个页面,第3级页表则有16K个页面。
为了节省宝贵的主存储器资源,通常除1级页表必须驻留在主存储器中之外,2级和3级页表只需要驻留一小部分。只要把目前正在运行中的程序的相关页表,或者把已经调入到主存储器中的程序的相关页表驻留在主存储器中。绝大部分页表可以放在磁盘存储器中。当一个用户程序被调入主存储器时,再把相关的页表也同时装入主存储器中,并且修改页表中的装入位和主存地址等字段。 采用多级页表(包括段表)使得访问主存储器的次数又要增加。例如,对于一个采用三级页表的页式虚拟存储器,要想从主存储器中取出一个数据,必须依次查三次页表才能得到这个数据在主存储器中的物理地址。包括读取这个数据本身在内,共需要访问四次主存储器。
要想使虚拟存储器的速度接近主存储器的速度。或者说,要想使虚拟存储器能够真正实用,必须加快查表的速度。

3.2.3.1 目录表

目录表的基本思想是:压缩页表的存储容量,用一个容量比较小的高速存储器来存放页表,从而加快页表的查表速度。

通常,多用户虚存空间要比主存储器的实存空间大得多。那么,在页式和段页式虚拟存储器中,虚存页面数就比实存页面数多得多。从页表中看,装入位为"1"(表示相应的页面不在主存储器中)的页面存储字所占的比例很小。
有一种压缩页表的方法。页表只为已经装入到主存储器中的那些页面建立虚页号与实页号之间的对应关系。页表的每一个存储字中主要包括多用户虚页号、主存实页号、修改位和其它标志(如访问方式)等,不再需要装入位,并且采用相联方式访问。因此,把这种目录表称为相联目录表,简称目录表。
采用目录表法的虚拟存储器,其地址变换过程如图3.26所示。为了把多用户虚页号变换成主存实页号,要把多用户虚地址中的多用户虚页号(U与P拼接起来)与相联存储器中的多用户虚页号字段逐个进行比较。如果有相等的,表示要访问的这个页面已经装入到主存储器中了。这时,读出该单元中的其它字段,其中,实页号字段中存放的就是与多用户虚页号相对应的主存实页号。只要把这个实页号p与多用户虚地址中的页内偏移D直接拼接起来就成了要访问的主存实地址。如果没有相等的,则表示要访问的那个页面还没有装入到主存储器中,这时,发出页面失效请求,从磁盘存储器中把要访问的那一个页面调入主存储器。
由于目录表是采用高速度小容量存储器实现的,与页表放在主存储器中的方法相比,查表的速度要快得多。
随着主存储器容量的增加,目录表的容量也必须增加。因此,目录表法的可扩展性比较差。当主存储器的容量增加到一定数量后,目录表的造价就会很高,查表的速度也会降低。

图3.26 目录表法的地址变换过程

3.2.3.2 快慢表

由于程序在执行过程中具有局部性特点,因此,对页表中各存储字的访问并不是随机的。也就是说,在一段时间内,对页表的访问只是局限在少数几个存储字内。根据这一特点,可大大缩小目录表的存储容量。例如,容量为8~16个存储字,访问速度与CPU中的通用寄存器相当。这个小容量的页表称为快表,快表采用与目录表相同的相联方式访问。当快表中查不到时,再从存放在主存储器中的页表中查找实页号。与快表相对应,存放在主存储器中的页表称为慢表。慢表是一个全表,快表只是慢表的一个副本,而且只存放了慢表中很少的一部分。
实际上,快表与慢表也构成了一个由两级存储器组成的存储系统。与虚拟存储器和Cache存储器类似。在这个快、慢表的存储系统中,访问速度接近于快表的速度,存储容量是慢表的容量。
快表在英文资料中称为TLB(Translation Lookaside Buffer),有些中文资料中翻译成地址变换后行缓冲器,或地址转换后备缓冲存储器等。

由快慢表构成的虚拟存储器的地址变换过程如图3.27所示。用多用户虚页号同时去查快表和慢表。由于快表的查表速度要比慢表快得多,如果在快表中查到与多用户虚地址相等的存储字,就立即终止慢表的查表过程,并读出该存储字中的实页号p送入到主存储器的地址寄存器中。如果在快表中没有查到,就到存放在主存储器中的慢表中去找。如果在慢表中查到了,则把查到的实页号p送入主存储器的地址寄存器,同时,也把这个实页号连同多用户虚地址等信息送入快表中。这时,若快表已满,则要采用某种替换算法,替换掉其中一个不常用的存储字。
由于快表的查表速度非常快,与主存储器的一个存储周期相比几乎可以忽略不计。因此,只要快表的命中率很高,那么,虚拟存储器的访问速度就能与主存储器的工作速度很接近。

图3.27 采用快慢表的地址变换过程

3.2.3.3 散列函数

在采用快慢表系统结构的虚拟存储器中,要提高快表的命中率,最直接的办法是增加快表的容量。快表的容量越大,命中率就越高。但是,由于快表是按相联方式访问的,当快表的容量增加时,它的查表速度就会降低。那么,能不能让快表不采用相联方式访问,而采用普通的按地址来访问呢?
如果要在一个按地址访问的存储器中查找一个信息,可以有随序查找法、对分查找法和散列查找法等。其中,散列(Hashing)查找方法的速度最快。对于快表来说,就是要把多用户虚页号Pv变换成快表的地址A。函数关系是

AhHPv

散列函数的种类很多。由于快表中的散列函数必须用硬件来实现,因此,通常采用一些简单的函数关系。图3.28是一种折叠按位加散列函数,把15位多用户虚地址Pv(内容)散列变换成6位的快表地址Ah。
由于把一个大的多用户虚页号Pv散列变换成了一个小的快表地址Ah,因此,必然会有很多个多用户虚页号Pv都散列变换到相同的快表地址Ah中,这种现象称为散列冲突。

图3.28 一种用硬件实现的散列函数

例如,在图3.28中,平均会有215÷26=29,即512个多用户虚页号全部被散列变换成同一个快表地址。
为了避免因散列冲突而发生查快表时的错误,必须把多用户虚页号也加入到快表中去,并且与主存实页号存放在同一个快表存储字中。另外,还要用一个比较器,把从快表中读出来的多用户虚页号与多用户虚地址中多用户虚页号进行比较。

采用散列变换实现快表按地址访问的虚拟存储器如图3.29所示。地址变换的过程是这样的:首先把多用户虚地址中的多用户虚页号Pv(由U和P拼接而形成)送入硬件的散列变换部件,经散列变换后得到快表的地址Ah。然后用地址Ah访问快表,读出主存实页号p和多用户虚页号Pv'。把主存实页号p送入主存储器的地址寄存器,与页内片偏移d直接拼接起来形成主存地址,并且用这个地址立即去访问主存储器。同时,把读出的多用户虚页号Pv'与多用户虚地址中多用户虚页号Pv在相等比较器中进行比较。如果比较结果相等,就继续刚才的访问主存储器的操作,否则,立即终止访问主存储器的操作。比较结果不相等表示发生了散列冲突,这时,需要去查存放主存储器中的慢表。查慢表的方法和快表的替换方法与上面介绍的采用相联存储器做快表的虚拟存储器相同。

图3.29 采用散列变换实现快表按地址访问

由于快表按地址访问,在保证访问速度的前提下,其存储容量可以比用相联存储器实现得快表大很多倍。虽然也有一次相等比较,但相等比较可以与访问存储器的操作同时进行。因此,这种快表按地址访问的快慢表页表结构,与上面介绍的采用相联存储器做快表的快慢表结构相比,快表的命中率要高很多,而且查表的速度也很快。

 3.2.3.4 虚拟存储器举例

下面,介绍两个在机器中实际使用的虚拟存储器。
  例1:IMB370/168计算机的虚拟存储器快表结构及地址变换过程。
如图3.30所示。IMB370/168计算机的采用页式虚拟存储器,虚拟地址共长36位。
其中,页面大小为4K字节,占12位。每个用户(或每道程序)最多允许占用4K个页面,因此,虚页号也占12位。最多允许16G个用户(或16G道程序)同时上机,用户号占24位,但是,实际上在一段时间内同时上机的用户数(或程序道数)一般不超过6个。
  例2Intel i486处理机的虚拟存储器的分段与分页机制。

图3.30 IBM370/168计算机的虚拟存储器快表结构

1IMB370/168计算机的虚拟存储器快表结构及地址变换过程
其中,页面大小为4K字节,占12位。每个用户(或每道程序)最多允许占用4K个页面,因此,虚页号也占12位。最多允许16G个用户(或16G道程序)同时上机,用户号占24位,但是,实际上在一段时间内同时上机的用户数(或程序道数)一般不超过6个。
快表按地址访问,快表的地址由多用户虚页号经硬件的散列变换部件压缩后生成。快表地址共6位,因此,快表容量为64个存储字。
    IMB370/168计算机的虚拟存储器采用了两项新的措施:
    1、在快表的每一个存储字中存放两对多用户虚页号与主存实页号,并采用两个相等比较器。只要其中有一个比较器的比较结果相等,就认为快表命中。这时,立即把命中的实页号p送到主存地址寄存器中,与地址寄存器中的页内偏移拼接成主存实地址。只有当两个比较器的比较结果都不相等时,才认为快表没有命中,需要到慢表中去查主存实页号p。查慢表的方法与前面介绍的方法相同。
    2、用一个由6个寄存器组成的相联寄存器组把24位的用户号U压缩成3位的ID。把这个ID与虚页号直接拼接起来作为散列变换部件的输入。这样做能够减少散列部件的输入与输出位数的差,从而降低散列冲突。
  在地址变换开始时,把多用户虚地址中用户号U与相联寄存器组中的6个用户号U1至U6逐个比较。如果有相等的,就读出对应的3位ID,并把这3位ID与多用户虚地址中的12位虚页号P直接拼接成15位,用这15位作为散列变换部件的输入。这样,在快表中可以同时保留6个用户(或6道程序)的常用页表。在6个用户或程序之内切换时,仍能保持很高的快表命中率。如果用户号U与6个相联寄存器的比较结果都不相等,则表示有6个之外的新用户进入。这时,需要用替换算法把一个不常进入的用户替换出相联寄存器组。只要替换算法好,就能保证经常进入的用户,或系统服务任务被一直保留在相联寄存器组中或很少被替换出去。
目前,许多计算机系统的虚拟存储器都采用象IBM370/168机器类似的方法。包括相联寄存器组,硬件的散列压缩,快慢表结构和多个相等比较器等。而且,所有这些措施对应用程序员来说都是透明的。

图3.31 Intel i486 CPU的分页与分段机制



2 Intel i486处理机的虚拟存储器的分段与分页机制。
    i486的虚拟存储器与80x86系列中的其处理机一样,兼有分段和分页的特点。保护方式采用4级保护,使线性地址从4GB扩展到64TB。在实方式下,最大存储容量是1MB。在保护方式下可以运行8086、80286及80386处理机的所有软件。每个段的长度可以从1个字节到它的最大物理存储容量4GB。
段可以在任意的基地址上开始,并允许段间有存储重叠。如图3.31(a)所示,虚拟地址有一个16位的段选择器,用以确定由i486分页系统使用的线性地址空间的基地址。
32位的偏移用来指定一个段的内部地址。段描述符除了用来指出段的起始地址之外,还用来指定段的长度和该段的存取方式等。
分页特性在i486中是任选的,可以通过软件来控制它。当允许分页时,虚拟地址首先被变换成由虚页号和页内偏移组成的线性地址,然后再变换成主存物理地址。当禁止分页时,线性地址和主存物理地址是相同的。当选择的段为4GB时,整个主存储器就成为一个大段,这相当于分段机制实际上已经被禁止。因此,i486处理机可以采用四种不同的方式来组织它的存储器系统。页式虚拟存储器、段式虚拟存储器、段页式虚拟存储器及纯物理寻址的主存储器等。
如图3.31(b)表示,快表(TLB)可以将线性地址直接变换成主存物理地址,不需要象图3.30(b)那样采用两级页表。
    i486的页面大小是4KB。图3.31(c)中的4个控制寄存器用来选择不同的页表目录。页表目录的大小为4KB,共有1024个页表存储字。第二级页表,每个页表的大小也是4KB,能保存1024页面的信息。快表和两级页表的命中率取决于具体的程序和所采用的页面替换算法。以前的观察结果表明,快表的命中率在98%左右。

3.2.4.1 页面替换算法

目前。在虚拟存储器常用的页面替换算法有如下几种:
1、 随机算法,即RAND算法(Random algorithm)。
利用软件或硬件的随机数发生器来确定主存储器中被替换的页面。这种算法最简单,而且容易实现。但是,这种算法完全没有利用主存储器中页面调度情况的历史信息,也没有反映程序的局部性,所以命中率比较低。
2、 先进先出算法,即FIFO算法(First-In First-Out algorithm)。
这种算法选择最先调入主存储器的页面作为被替换的页面。它的优点是比较容易实现,能够利用主存储器中页面调度情况的历史信息,但是,没有反映程序的局部性。因为最先调入主存的页面,很可能也是经常要使用的页面。
3、 近期最少使用算法,即LFU算法(Least Frequently Used algorithm)。
这种算法选择近期最少访问的页面作为被替换的页面。显然,这是一种非常合理的算法,因为到目前为止最少使用的页面,很可能也是将来最少访问的页面。该算法既充分利用了主存中页面调度情况的历史信息,又正确反映了程序的局部性。但是,这种算法实现起来非常困难。它要为每个页面设置一个很长的计数器,并且要选择一个固定的时钟为每个计数器定时计数。在选择被替换页面时,要从所有计数器中找出一个计数值最大的计数器。因此,通常采用另外一种变通的办法,就是下面的LRU算法。
4、 最久没有使用算法,即LRU算法(Least Recently Used algorithm)。
这种算法把近期最久没有被访问过的页面作为被替换的页面。它把LRU算法中要记录数量上的"多"与"少"简化成判断"有"与"无",因此,实现起来比较容易。
5、 最优替换算法,即OPT算法(OPTimal replacemant algorithm)。
上面介绍的几种页面替换算法主要是以主存储器中页面调度情况的历史信息为依据的,它假设将来主存储器中的页面调度情况与过去一段时间内主存储器中的页面调度情况是相同的。显然,这种假设不总是正确的。最好的算法应该是选择将来最久不被访问的页面作为被替换的页面。这种替换算法的命中率一定是最高的。这就是最优替换算法,简称OPT算法。
要实现OPT算法,唯一的办法是让程序先执行一遍,记录下实际的页地址流情况。根据这个页地址流才能找出当前要被替换的页面。显然,这样做是不现实的。因此,OPT算法只是一种理想化的算法,然而,它也是一种很有用的算法。实际上,经常把这种算法用来作为评价其它页面替换算法好坏的标准。在其它条件相同的情况下,哪一种页面替换算法的命中率与OPT算法最接近,那么,它就是一种比较好的页面替换算法。

1一个程序共有5个页面组成,分别为P1~P5。程序执行过程中的页地址流(即程序执行中依次用到的页面)如下:P1P2P1P5P4P1P3P4P2P4假设分配给这个程序的主存储器共有3个页面。图3.32表示FIFO、LRU和OPT三种页面替换算法对这3页主存的使用情况,包括调入、替换和命中等。图中,用"*"号标记下次将要被替换掉的页面。
在上面介绍的5种页面替换算法中,随机算法的命中率比较低,一般仅用于必须用硬件实现,而且对命中率要求不太高的场合。LFU算法由于其实现起来特别困难,目前很少被采用。因此,在虚拟存储器中,实际上有可能被采用的页面替换算法就只有FIFO和LRU两种。

图3.32 三种页面替换算法对同一个页地址流的调度过程

从图3.32中可以看出,FIFO算法的命中率最低,LRU算法的命中率与OPT算法很接近。这一结论具有普遍意义。因此,在实际使用中,LRU算法是一种比较好的算法。目前,许多机器的虚拟存储器都采用LRU算法。

2一个循环程序,依次使用P1,P2,P3,P4四个页面,分配给这个程序的主存页面数为3个。FIFO、LRU和OPT三种页面替换算法对主存页面的调度情况如图3.33所示。OPT算法命中3次,而FIFO和LRU算法一次也没有命中。在FIFO和LRU算法中,总是发生下次就要使用的页面本次被替换出去了。这就是"颠簸"现象。
一般来说,对于一个循环程序,当分配给它的页面数小于程序所需要的页面数数时,有可能发生"颠簸"现象。这时,无论是FIFO算法,还是LRU算法,它们的命中率都明显地低于OPT算法。

图3.33 页面调度中的颠簸现象

对于这个例子,只要再多分配给它一个页面,三种算法的命中率都能达到4次(最大值)。因此,命中率不仅与页地址流有关,而且也与分配给程序的主存页面数等有关。
一般来说,分配给程序的主存页面数越多,虚页装入到主存到中的机会也就越多,因此命中率也可能越高,至少不应该下降。

3.2.4.2 堆栈型替换算法

这里所说的堆栈型替换算法不是指某一种算法,也不是指先进先出或先进后出的堆栈本身,而是指一类算法。
在主存页面的分配和调度中,影响命中率的因素很多,这给页面分配和调度造成了困难。那么,能否减少一些因素,或者把某些因素简化一些呢?
堆栈型算法就是要研究影响命中率的一个主要因素,即分配给程序的主存页面数的增加与命中率的关系。弄清了这个问题,不需要做大量的模拟工作,就能知道如何为程序分配主存页面数。
那么,什么是堆栈型替换算法呢?

堆栈型替换算法:对任意一个程序的页地址流作两次主存页面数分配,分别分配m个主存页面和n个主存页面,并且有m≤n。如果在任何时刻t,主存页面数集合Bt都满足关系:

Btmí Btn

则这类算法称为堆栈型替换算法。简单地说,堆栈型算法的基本思想是:随着分配给程序的主存页面数增加,主存的命中率也提高,至少不下降。
对于LFU算法和LRU算法,由于在主存中保留的是最近使用过的页面。如果先给某一个程序分配n个主存页面,那么在t时刻,这n个主存页面都是最近使用过的页面。如果再给这个程序多分配一个主存页面,那么在t时刻,这n+1个主存页面也都是最近使用过的页面。因此,在这n+1个主存页面中必然包含了前面的n个主存页面。所以,LFU算法和LRU算法都是堆栈型算法。很显然,OPT算法也是堆栈型算法。那么,FIFO算法是不是堆栈型算法呢?请看图3.34的情况。

图3.34 FIFO算法在主存页面数增加时命中率反而下降

在图3.34中,对于同一个页地址流,当分配给这个程序的主存页面数从3页增加到4页时,命中率反而从3次下降到2次。因此,FIFO算法不是堆栈型算法。
由于堆栈型替换算法的命中率随分配个该程序的主存页面增加而单调上升,因此,在多道程序系统中,可以采用一种被称为页面失效频率法(PFF:Page Fault Freguency)的动态页面调度方法。具体做法是:根据各道程序在实际运行过程中页面失效率的情况,由操作系统动态调整分配给每道程序的主存页面数。当一道程序的命中率低于某个限定值时就增加分配给该道程序的主存页面数,以提高它的命中率。而当命中率高于某个限定值时就减少分配给该道程序的主存页面数,把节省出来的主存页面分配给其它程序。从而使整个系统的总的命中率和主存利用率都得到提高。

3.2.4.3 页面替换算法的实现

虚拟存储器中的主存页面替换算法一般用软件实现。操作系统为了实现对主存储器的管理,设置有一个主存页面表。页面表中每一个存储单元记录主存储器中一个页面的当前使用状况。显然,主存页面表并不是前面介绍的那个页表。页表是用来保存虚页号与主存实页号之间的映象关系,并且在程序运行过程中实现地址变换用的。它是面向用户程序空间的,每一个用户,或者每一道程序都有一张页表。而主存页面表是面向主存储器的,整个主存储器只有一张主存页面表。主存页面表存放在主存储器中,它所记录的主要内容如图3.35所示。

图3.35 主存页面表的结构(P为主存页面数)

实页号

占用位

程序号

段页号

使用位
(计数器)

程序优先级

历史位Hb
(未使用计数器)

其它信息

0

1

.
.
.

P-1

因为主存页面表中的每一个存储字对应与主存储器中的一个页面,而且是按顺序存放的,因此实页号这一字段可以省略。占用位用来表示哪些页面已经被占用。占用位为"0"表示该页面是空的,没有被占用,占用位为"1"则表示该页面已经被占用。程序号和段页号表示该页面是被哪个程序的哪个段、哪个页占用。
为了实现FIFO替换算法,主存页面表中使用位字段应该设置成一个计数器。每当有一个页面装入主存储器时,就让该页面的计数器清零,而其它已经装入到主存储器中的页面所对应的计数器加"1"。在需要替换时,只要找出计数器的值最大的页面就是最先进入主存储器的页面。
实现LRU算法也相当容易。在一般情况下,只需要一个使用位即可。开始时,所有页面的使用位全为"0",当一个页面中的任何一个存储单元被访问了,就把该页面的使用位置为"1"。这样,当主存储器的所有页面都已经被占用,即所有占用位均为"1"时,又有新的页面需要调入主存储器时,就要进行替换。替换的方法很简单,只要找出使用位为"0"的页面作为被替换的页面即可。
如果所有页面的使用位已经全部为"1",又要调入新页时,就没有办法确定要被替换的页面。因此,采用使用位来实现LRU算法时,不允许所有页面的使用位全部为"1"的情况发生。有以下几个办法可以解决这个问题。

一种简单的办法是,当所有页面的使用位都为"1"时,就立即把所有页面的使用位全部清成"0"。因此,LRU算法中的这个"近期"就是指从上次所有使用位全部清为"0"到这次使用位全部为"1"这一段时间。在一般情况下,这一段时间是不固定的。这种方法很简单,而且容易实现。
第二种办法是定期法。每间隔一个固定的时间,如几个毫秒或几秒。把所有页面的使用位全部清为"0"。那么,这时候的"近期"就是一个固定的时间间隔。当然,这个固定的时间间隔要选择得恰当。既要保证不发生全部使用为位都为"1"的情况,又要使这个时间间隔尽可能长些。
另一种比较好的办法是,在主存页面表中再设置一个历史位Hb,或者叫做"未使用计数器"。每间隔一段时间扫描所有页面的使用位。如果使用位为"0",则把对应的Hb加"1",否则,把Hb清为"0"。同时,无论使用位原来是"0"还是"1",都把它们清为"0"。这样,扫描结束时,所有页面的使用位就都是"0",相当于又开始了一个"近期"。这种方法与上面两种方法的不同处在于,它把每一个"近期"都记录在Hb中了。因此,Hb的值越大,说明所对应的页面最久没有被访问过,就应该成为最先被替换掉的页面。实质上,前两种方法只反映了一个"近期"内主存页面的使用情况,而后一种方法能反映多个"近期"内主存页面的使用情况。

3.2.5 提高主存命中率的方法

在本章第一部分中,曾经给出一个存储系统的等效访问速度的计算公式:
        T=H·T1+(1-H)·T2
在虚拟存储器中,T1是指主存储器的访问周期,其中包括查页表和段表等所需要的时间。

在本章第三节中已经介绍了用目录表、快慢表和散列函数等方法来缩短查表的时间,并且介绍了提高快表命中率的一些方法。当快表的命中率很高时,查页表和段表所化的时间与主存储器的存储周期相比几乎可以忽略不计。因此,公式中的T1就与主存储器访问周期很接近。而T2是指磁盘存储器的访问速度。由于当主存储器不命中时,一般用软件来调度,因此,T2通常是很大的。
从上面的分析中可以看出,要提高虚拟存储器的等效访问速度,提高主存的命中率是一个非常关键的因素。
影响主存命中率的主要因素有如下几个:
    1、程序在执行过程中的页地址流分布情况。
    2、所采用的页面替换算法。
    3、页面大小。
    4、主存储器的容量
    5、所采用的页面调度方法。
在这些因素中,页地址流的分布情况是由程序本身决定的,系统设计人员一般无能为力。页面替换算法,在上一节中已经介绍过。目前,多数机器都采用LFU算法。它是一种堆栈型算法。在当前看来,已经是一种比较好的算法了。以下,对影响主存命中率的另外三个因素作简单的分析。

3.2.5.1 页面大小的选择

页面大小与主存命中率的关系不是线性的,它还与主存储器的存储容量和程序的页地址流分布情况等多种因素有关。在图3.36中给出页面大小Sp、主存容量S与命中率H的关系曲线。

图3.36 页面大小与主存命中率的关系

从图3.36中可以看出,在分配给程序的主存容量S一定的情况下,当页面大小Sp比较小的时侯,命中率H随页面大小Sp的增大而逐渐提高。当页面大小增加的某个值时,命中率达到最大。然后,随着页面大小的增加,命中率反而降低。另外,从两条曲线的比较中可以看出,当分配给程序的主存容量S增大时,命中率能普遍提高,关于命中率与主存容量的关系,在下面的一小节中还要介绍。
图3.36的曲线关系可以这样来解释:在程序执行过程中,假设At和At+1是两次相邻的访问主存储器的逻辑地址,
d=|At-At+1|。如果d<Sp,那么,随着Sp的增大,At和At+1在同一个页面内的可能性就会增加,即H随着Sp的增大而提高。如果d>Sp,那么,At和At+1一定不在同一个页面内。随着Sp的增大,在分配给该程序的主存容量一定的情况下,主存的页面数就要减少,页面的替换将更加频繁。这样,At和At+1两个地址所在的页面都在主存储器中的可能性就会减少,即H随着Sp的增大而降低。当Sp比较小的时候,前一种情况是主要的,因此,H随着Sp的增大而提高。当Sp达到某一个最大值之后,后一种情况成为主要的,因此,H随着Sp的增大而降低。
  在设计一个虚拟存储器时,页面大小的选择一般要通过对典型程序的模拟实验来确定。早期的许多计算机系统,页面大小一般为1KB。目前,大多数机器的页面大小都取4KB、8KB,或16KB。
另外,当页面大小增大时,由于每个程序或程序段的最后一个页面一般是装不满的,由此造成的浪费也要增加。相反,当页面大小减小时,页表(指慢表)和页面表在主存储器中所占的比例将增加。这两种情况都要降低主存储器的利用率。因此,页面大小的选择要综合考虑多方面的因素。

3.2.5.2 主存容量

主存命中率H随着分配给该程序的主存容量S的增加而单调上升,如图3.37所示。在S比较小的时候,H提高得非常快。随着S的逐渐增加,H提高的速度逐渐降低。当S增加到某一个值之后,H几乎不再提高。

在页面替换算法中有这样一个结论,对于堆栈型算法,命中率随着分配给程序的页面数的增加而提高。当分配给程序的主存容量增加时,如果页面大小是一定的,那么,页面数就会增加,因此,命中率也将提高。如果不是堆栈型算法,命中率虽然不会单调上升,在局部可能有下降,但总的趋势还是上升的。
从图3.37中可以得到这样一个启发,在为一道程序分配主存空间时,对主存命中率的要求不能过分。当主存容量增加到某一个值之后,命中率的提高非常慢。这时,主存储器中不活跃部分所占的比例很大,主存资源的利用率就会很低。

图3.37 主存命中H率与主存容量S的关系

实际上,操作系统在为程序分配主存空间时,是以页为单位分配的。因此,图3.37所示的不应该是一条平滑的曲线,而是台阶型的。在分配给程序的主存容量比较小的时候,台阶非常陡,随着主存容量的增加,台阶越来越平缓。

3.2.5.3 页面调度方式

在操作系统中,页面调度通常有两种方式。一种是分页式,另一种方式是请求页式。

分页式:这种方式在程序装入主存储器之前就对程序进行链接装配,并且要把整个程序都调入到主存储器中之后才能开始运行。
请求页式:这种方式只在发生页面失效时,才把要访问的页面进行链接装配,并调入到主存储器中。
前一种方式的主存命中率可以达到100%。但是,主存利用率比较低,这是因为主存储器中不活跃部分所占的比例比较大。而且,当主存剩余空间小于程序所需要的主存空间时,这个程序将无法装入到主存储器中运行。
目前,大多数机器采用请求页式调度方式。这种方式虽然具有主存利用率高,只要主存储器还剩余有一个页面,程序就能调入到主存储器中开始运行等优点,但是,在程序执行过程中经常要发生页面失效,而且处理页面失效需要比较长的时间。特别在程序刚开始运行时,页面失效很频繁。从前面的图3.32中也可以看出,无论采用什么样的页面替换算法,在程序开始执行时的一段时间内,都在不断地调入页面,主存命中率很低。只有当调入的页面数比较多时,命中率才开始上升。因此,就产生了另外一种折中的调度方式。即所谓预取式调度方式。
预取式调度方式:根据程序的局部性特点,在程序被挂起之后又重新开始运行之前,先把上次停止运行前一段时间内用到的页面先调入到主存储器,然后才开始运行程序。
经过预取调度之后,在程序一开始运行时,主存储器中就已经装入了一定数量的页面。从而可以避免在程序刚开始运行时,频繁发生页面失效的情况。当然,这种调度策略也不一定总是有效的。如果调入的页面在程序开始运行后用不上,那么,它不仅在调入这些页面的过程中浪费了时间,而且还占用了主存资源。

你可能感兴趣的:(虚存管理)