四、虚拟内存的基本概念
1. 传统存储方式的特征
上一节所讨论的各种内存管理策略都是为了同时将多个进程保存在内存中以便允许多道程序设计。它们都具有以下两个共同的特征:
-
一次性
作业必须一次性全部装入内存后,方能开始运行。这会导致两种情况发生:
- 当作业很大,不能全部被装入内存时,将使该作业无法运行;
- 当大量作业要求运行时,由于内存不足以容纳所有作业,只能使少数作业先运行,导致多道程序度的下降。
-
驻留性
作业被装入内存后,就一直驻留在内存中,其任何部分都不会被换出,直至作业运行结束。运行中的进程,会因等待I/O而被阻塞,可能处于长期等待状态。
2. 局部性原理
要真正理解虚拟内存技术的思想,首先必须了解计算机中著名的局部性原理。著名的 Bill Joy (SUN公司CEO)说过:”在研究所的时候,我经常开玩笑地说高速缓存是计算机科学中唯一重要的思想。事实上,髙速缓存技术确实极大地影响了计算机系统的设计。“快表、 页高速缓存以及虚拟内存技术从广义上讲,都是属于高速缓存技术。这个技术所依赖的原理就是局部性原理。局部性原理既适用于程序结构,也适用于数据结构(更远地讲,Dijkstra 著名的关于“goto语句有害”的论文也是出于对程序局部性原理的深刻认识和理解)。
局部性原理表现在以下两个方面:
- 时间局部性:如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
- 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。
3. 虚拟内存的定义与特征
基于局部性原理,在程序装入时可以将程序中很快用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存。虚拟内存有以下三个特征:
- 多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存;
- 对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入换出;
- 虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际容量。
知识扩展:
虚拟内存的最大容量是由计算机的地址结构(CPU寻址范围)确定的,虚拟内存的实际容量 = min(内存和外存的容量之和,CPU寻址范围);
如:某计算机地址结构为32位,按字节编址,内存大小为512 MB,外存大小为 2 GB。则虚拟内存的最大容量为 2^32 B = 4 GB,虚拟内存的实际容量 = min (2^32 B, 512 MB + 2 GB) = 2 GB + 512 MB。
4. 如何实现虚拟内存技术
虚拟内存技术,允许一个作业多次调入内存。如果采用连续分配方式,会不方便实现。因此,虚拟内存的实现需要建立在离散分配的内存管理方式基础上。虚拟内存的实现有三种方式:①请求分页存储管理;②请求分段存储管理;③请求段页式存储管理。
不管哪种方式,都需要有一定的硬件支持。一般需要的支持有以下几个方面:
- 一定容量的内存和外存。
- 页表机制(或段表机制),作为主要的数据结构。
- 中断机构,当用户程序要访问的部分尚未调入内存,则产生中断。
- 地址变换机构,逻辑地址到物理地址的变换。
五、请求式分页管理
请求分页系统建立在基本分页系统基础之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。是目前最常用的一种实现虚拟存储器的方法。在请求分页系统中,只需求将当前的一部分页面装入内存,便可以启动作业运行。在作业执行过程中,当所要访问的页面不存在时,再通过调页功能将其调入,同时还可以通过置换功能将暂时不用的页面换出到外存上,以便腾出内存空间。
为了实现请求分页,系统必须提供一定的硬件支持。除了需要一定容量的内存及外存的计算机系统,还需要有页表机制、缺页中断和地址变换机构。
1. 页表机制
与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要直到每个页面是否已经调入内存;如果还没调入,那么也需要知道该页面在外存中存放的位置。
当内存中间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面:有的页面没有被修改过,就不用再浪费时间写回外存。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。
2. 缺页中断机构
在请求分页系统中,每当要访问的页面不存在时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。
如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
一条指令在执行期间,可能产生多次缺页中断。(如:copy A to B)。
3. 地址变换机构
请求分页系统中的地址变换机构,是在分页系统地址变换机构的基础上,为实现虚拟内存,又增加了某些功能而形成的。
新增步骤1:请求调页(查到页表项时进行判断);
新增步骤2:页面置换(需要调入页面,但没有空闲内存块时进行)
新增步骤3:需要修改请求页表中新增的表项。
细节补充:
- 只有“写指令”才需要修改“修改位”。并且,一般来说只需修改快表中的数据,只有要将快表项删除时才需要写回内存中的慢表。这样可以减少访存次数;
- 和普通的中断处理一样,缺页中断处理依旧需要保存CPU现场;
- 需要用某种“页面置换算法”来决定一个换出页面;
- 换入/换出页面都需要启动慢速的I/O操作,可见,如果换入换出太过频繁,会有很大开销;
- 页面调入内存后,需要修改慢表,同时也需要将表项复制到快表中。
六、页面置换算法
进程运行时,若其访问的页面不在内存而需将其调入,但内存已无空闲空间时,就需要从内存中调出一页程序或数据,送入磁盘的对换区。选择调出页面的算法就称为页面置换算法。好的页面置换算法应有较低的页面更换频率,也就是说,应将以后不会再访问或者以后较长时间内不会再访问的页面先调出。
常见的页面置换算法有以下几种:
1. 最佳置换算法(OPT)
最佳置换算法(POT,Optimal):每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。
例:假设系统为某进程分配了三个内存块,并考虑到有以下页面号引用(会依次访问这些页面):
7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1
进程运行时,先将 7, 0, 1 三个页面依次装入内存,前三次都会发生缺页中断(因为还没被访问过,第一次加载进来)。接下来进程要访问页面2时,产生缺页中断,根据最佳置换算法,选择第18次访问才需调入的页面7予以淘汰。然后,访问页面0时,因为已在内存中所以不必产生缺页中断。访问页面3时又会根据最佳置换算法将页面1淘汰……依此类推,如图3-26所示。从图中可以看出釆用最佳置换算法时的情况。
访问页面 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
物理块1 | 7 | 7 | 7 | 2 | 2 | 2 | 2 | 2 | 7 | |||||||||||
物理块2 | 0 | 0 | 0 | 0 | 4 | 0 | 0 | 0 | ||||||||||||
物理块3 | 1 | 1 | 3 | 3 | 3 | 1 | 1 | |||||||||||||
缺页否 | √ | √ | √ | √ | √ | √ | √ | √ | √ |
整个过程缺页中断发生了9次,页面置换发生了6次。缺页率 = 9/20 = 45%;
注意:
- 缺页时未必发生页面置换,若还有可用的空闲内存块,就不用进行页面置换;
- 最佳置换算法可以保证最低的缺页率,但实际上,只有在进程执行的过程中才能知道接下来要访问到的是哪个页面。操作系统无法提前预判页面访问序列。因此,最佳置换算法是无法实现的。
2. 先进先出置换算法(FIFO)
先进先出置换算法(FIFO):每次选择淘汰的页面是最早进入内存的页面。实现方法可以通过把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面即可。队列的最长取决于系统为进程分配了多少个内存块。
例:假设系统为某进程分配了三个内存块,并考虑到有以下页面号引用:
3 2 1 0 3 2 4 3 2 1 0 4
访问页面 | 3 | 2 | 1 | 0 | 3 | 2 | 4 | 3 | 2 | 1 | 0 | 4 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
物理块1 | 3 | 3 | 3 | 0 | 0 | 0 | 4 | 4 | 4 | |||
物理块2 | 2 | 2 | 2 | 3 | 3 | 3 | 1 | 1 | ||||
物理块3 | 1 | 1 | 1 | 2 | 2 | 2 | 0 | |||||
缺页否 | √ | √ | √ | √ | √ | √ | √ | √ | √ |
Belady现象:
FIFO算法还会产生当所分配的物理块数增大而页故障数不减反增的异常现象,这是由 Belady于1969年发现,故称为 Belady 异常,如图下表所示:
访问页面 | 3 | 2 | 1 | 0 | 3 | 2 | 4 | 3 | 2 | 1 | 0 | 4 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
物理块1 | 3 | 3 | 3 | 3 | 4 | 4 | 4 | 4 | 0 | 0 | ||
物理块2 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 4 | |||
物理块3 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | ||||
物理块4 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | |||||
缺页否 | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ |
为什么会出现Belady现象?原因:
FIFO算法的置换特征与进程访问内存的动态特征矛盾,被它置换出去的页面并不一定是进程近期不会访问的。
只有 FIFO 算法会产生 Belady 现象。另外,FIFO 算法虽然实现简单,但是该算法于进程实际运行时规律不适应,因为先进入的页面也有可能是最常访问的。因此,算法性能差。
3. 最近久未使用置换算法(LRU)
最近最久使用置换算法(LRU,least recently used)指每次淘汰的页面是最近最久未使用的页面。实现方法:赋予每个页面对应的页表项,用访问字段记录该页面自上次被访问以来所经历的时间t。当需要淘汰一个页面时,选择现有页面中 t 值最大的,即最近最久未使用的页面。
例:假设系统为某进程分配了四个内存块,并考虑到有以下页面号引用串:
1 8 1 7 8 2 7 2 1 8 3 8 2 1 3 1 7 1 3 7
访问页面 | 1 | 8 | 1 | 7 | 8 | 2 | 7 | 2 | 1 | 8 | 3 | 8 | 2 | 1 | 3 | 1 | 7 | 1 | 3 | 7 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
物理块1 | 1 | 1 | 1 | 1 | 1 | 1 | ||||||||||||||
物理块2 | 8 | 8 | 8 | 8 | 7 | |||||||||||||||
物理块3 | 7 | 7 | 3 | 3 | ||||||||||||||||
物理块4 | 2 | 2 | 2 | |||||||||||||||||
缺页否 | √ | √ | √ | √ | √ | √ |
如上表,若要淘汰页面,可以逆向检查此时在内存中的几个页面号。在逆向扫描过程中最后一个出现的页号就是要淘汰的页面。
该算法的实现需要专门的硬件支持,虽然算法性能好,但是实现困难,开销大。
4. 时钟置换算法(CLOCK)
简单 CLOCK 算法实现:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某个页面被访问时,其访问位为1。当需要淘汰一个页面时,只需要检查页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都为1,则将这些页面的访问位依次置为0之后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面,因此简单的CLOCK算法选择一个淘汰页面最多会进过两轮扫描)。
5. 改进型的时钟置换算法
简单的时间置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/O操作。这就是改进型的时钟置换算法的思想。
修改位=0,表示页面没被访问过;修改位位=1,表示页面被访问过。为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过,且被修改过。
算法规则:将所有可能被置换的页面排成一个循环队列
- 第一轮:从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位;
- 第二轮:若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0;
- 第三轮:若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位;
- 第四轮:若第三轮扫描失败,则重新扫描,查找地一个(0,1)的帧用于替换。
由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描一定会有一个帧被选中,因此改进型CLOCK置换算法选中淘汰一个页面最多会进行四轮扫描。
七、页面分配策略
1. 页面分配、置换策略
驻留集:指请求分页存储管理中给进程分配的物理块的集合。
在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小。若驻留集太小,会导致缺页频繁,系统要花大量的时间来处理缺页,实际效率降低;若驻留集太大,又会导致多道程序并发度下降,资源利用率降低。所以应该综合考虑驻留集的大小。
固定分配与可变分配:固定分配指操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。即驻留集大小不变;可变分配是指先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。即,驻留集大小可变。
局部置换与全局置换:局部置换是指发生缺页时只能选进程自己的物理块进行置换;全局置换是指可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程。
基于上述因素,现代操作系统通常釆用三种策略:
- 固定分配局部置换:系统为每个进程分配一定数量的物理块,在整个运行期间都不改变。若进程在运行中发生缺页,则只能从该进程在内存中的页面中选出一页换出,然后再调入需要的页面。这种策略的缺点是:很难在刚开始就确定应为每个进程分配多少个物理块才算合理。(采用这种策略的系统可以根据进程大小、优先级、或是根据程序员给出的参数来确定为一个进程分配的内存块数);
- 可变分配全局置换:刚开始会为每个进程分配一定数量的物理块。操作系统会保持一个空闲物理块队列。当某进程发生缺页时,从空闲物理块中取出一块分配给该进程;若已无空闲物理块,则可选择一个未锁定(系统会锁定一些重要页面,这些数据不能被置换出外存),再将该物理块分配给缺页的进程。采用这种策略时,只要某进程发生缺页,都将获得新的物理块,仅当空闲块用完时,系统才选择一个未锁定的页面调出。被选择调出的页可能是系统中任何一个进程中的页,因此这个被选中的进程拥有的物理块会减少,缺页率会增加;
- 可变分配局部置换:刚开始会为每个线程分配一定数量的物理块。当某线程发生缺页时,只允许从该进程自己的物理块中选出一个进行换出外存。如果进程在运行中频繁的缺页,系统会为该进程多分配几个物理块,直至该进程缺页率趋势适当程度;反之,如果进程在运行中缺页率特别低,则可适当减少分配给该进程的物理块;
- 固定分配全局置换:全局置换意味着一个进程拥有的物理块数量必然会改变,因此不可能是固定分配,该策略页不可能实现。
2. 何时调入页面
为确定系统将进程运行时所缺的页面调入内存的时机,可釆取以下两种调页策略:
-
预调页策略。根据局部性原理,一次调入若干个相邻的页可能会比一次调入一页更高效。但如果调入的一批页面中大多数都未被访问,则又是低效的。所以就需要釆用以预测为基础的预调页策略,将预计在不久之后便会被访问的页面预先调入内存。但目前预调页的成功率仅约50%。故这种策略主要用于进程的首次调入时,由程序员指出应该先调入哪些页。
-
请求调页策略。进程在运行中需要访问的页面不在内存而提出请求,由系统将所需页面调入内存。由这种策略调入的页一定会被访问,且这种策略比较易于实现,故在目前的虚拟存储器中大多釆用此策略。它的缺点在于每次只调入一页,调入调出页面数多时会花费过多的 I/O 开销。
3. 从何处调入页面
请求分页系统中的外存分为两部分:用于存放文件的文件区和用于存放对换页面的对换区。对换区通常是釆用连续分配方式,而文件区釆用离散分配方式,故对换区的磁盘I/O速度比文件区的更快。这样从何处调入页面有三种情况:
- 系统拥有足够的对换区空间:可以全部从对换区调入所需页面,以提髙调页速度。为此,在进程运行前,需将与该进程有关的文件从文件区复制到对换区。
- 系统缺少足够的对换区空间:凡不会被修改的文件都直接从文件区调入;而当换出这些页面时,由于它们未被修改而不必再将它们换出。但对于那些可能被修改的部分,在将它们换出时须调到对换区,以后需要时再从对换区调入。
- UNIX方式:与进程有关的文件都放在文件区,故未运行过的页面,都应从文件区调入。曾经运行过但又被换出的页面,由于是被放在对换区,因此下次调入时应从对换区调入。进程请求的共享页面若被其他进程调入内存,则无需再从对换区调入。
4. 抖动现象
在页面置换过程中的一种最糟糕的情形是,刚刚换出的页面马上又要换入主存,刚刚换入的页面马上就要换出主存,这种频繁的页面调度行为称为抖动,或颠簸。如果一个进程在换页上用的时间多于执行时间,那么这个进程就在颠簸。
频繁的发生缺页中断(抖动),其主要原因是某个进程频繁访问的页面数目高于可用的物理页帧数目。虚拟内存技术可以在内存中保留更多的进程以提髙系统效率。在稳定状态,几乎主存的所有空间都被进程块占据,处理机和操作系统可以直接访问到尽可能多的进程。但如果管理不当,处理机的大部分时间都将用于交换块,即请求调入页面的操作,而不是执行进程的指令,这就会大大降低系统效率。
5. 工作集
驻留集:指请求分页存储管理中给进程分配的内存块集合;
工作集:指在某段时间间隔里,进程实际访问页面的集合。
操作系统会根据“窗口尺寸”来计算工作集,例:
某进程的页面访问序列如下,窗口尺寸为4,各时刻的工作集为?
工作集大小可能小于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作集大小给进程分配若干内存块。如:窗口尺寸为5,经过一段时间的检测发现某进程的工作集最大为3,那么说明该进程有很好的局部性,可以给这个进程分配3个以上的内存块即可满足进程的运行需要。一般来说,驻留集大小不能小于工作集大小,否则进程运行过程中将频繁缺页。
扩展:基于局部性原理可知,进程在一段时间内访问的页面与不久之后会访问的页面是有相关性的。因此,可以根据进程近期访问的页面集合(工作集)来设计一种页面置换算法——选择一个不在工作集中的页面进行淘汰。