一 基本概念
(一) 传统存储管理的特征及缺点
之前讨论的内存管理方式都是为了多道运行环境,将多道程序同时装入内存中,但他们都有共同缺陷:
1.一次性 . 作业必须一次性全部装入内存,才能开始运行,这就导致了:
- 作业太大,装不下
- 作业太多,塞不下
2.驻留性. 作业一旦被装入内存,就会一直存在,直到运行结束,这就导致了:
- 若当前作业发生io操作,即使阻塞了,也会一直存在内存中
从以上分析可知,内存中由许多作业暂时无法运行,而一些准备就绪的作业却无法进入内存,浪费了大量的空间
(二) 局部性原理
1.时间局部性:
执行了程序的某条指令,在不久后很可能会再次执行这条指令
2.空间局部性
执行了某个存储单元,不久后,其附近的存储单元可能也会被访问
如这段代码:
int i =0;
int a[100];
while(i<100){
a[i]=i;
i++;
}
在极短的时间内,a[i]=i,i++被访问100次,因此便可利用缓存技术将这段常用的指令进行高速缓存
(三)计算机中存储器的层次结构
- 时间局部性是将近来经常访问的数据存储到高速缓存寄存器中
- 空间局部性适用的是较大的高速缓存
- 虚拟内存技术实际上是建立了"内存-外层"的两级存储器结构
(四) 虚拟内存的定义
利用局部性原理的定义,在程序装入时,可以将常用的部分装入到内存,其余部分留在外存,并在必要时刻进行:
- 若当前所访问的信息不存在,则由操作系统将所需的页调入内存(–也即请求调换的功能)
- 若内存空间不够,则利用一定的算法将某些不常用的部分调出到外存(–也即置换功能)
这样,在用户看来,就好似拥有了一个比实际大的多的内存,因此称为’虚拟内存’
(五) 虚拟内存的实现技术
若采用 连续分配方式,将使一部分连续的内存很长一段时间处在’暂停使用’或’空闲’的状态
因此,虚拟内存应基于离散的内存分配管理
主要存在以下三种方式:
- 请求分页式管理l
- 请求分段式管理
- 请求段页式管理
但无论哪一种,都需要以下几方面的支持
- 内外存
- 缺页中断机制
- 页表
- 地址转化
二 请求分页管理
请求分页与基本分页管理的区别在于,基本分页管理是一次性将程序导入内存,而请求分页是按需导入
(一) 页表
对比基本分页管理的页表,新增了四个存储字段
(二) 缺页中断
在请求分页系统中,若被访问的页面不存在内存中,进程便发起缺页中断,请求操作系统掉页进入内存.此时,发起中断的进程将被阻塞,放入阻塞队列,当调换结束后,再将其放入就绪队列
调换时,若内存中有空闲块,则系统为其分配内存
若不存在空闲块,则通过一定的置换算法,淘汰某些内存中的页(若该页在调入内存的过程中被修改过,则需要先将其写回外存)
(三) 地址转化
同样在基本分页管理的基础上,新增了一些步骤:
- 若在快表中找到要访问的页面,则修改页表项中的访问段,然后利用页表项中的内存块号和地址中的偏移量获得物理地址
- 若未找到,则需到内存中查找页表,再对比页表项中的状态位P,看该页是否已经调入内存.
- 若未调入内存,则发出缺页中断,请求调入
三 页面置换算法
选择调出页面的算法就称为页面置换算法。好的页面置换算法应有较低的页面更换频率,也就是说,应将以后不会再访问或者以后较长时间内不会再访问的页面先调出。
常见的置换算法有以下四种(以下来自操作系统课本)。
(一) 最佳置换算法(OPT)
最佳(Optimal, OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。
最佳置换算法可以用来评价其他算法。假定系统为某进程分配了三个物理块,并考虑有以下页面号引用串:
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所示。从图中可以看出釆用最佳置换算法时的情况。
可以看到,发生缺页中断的次数为9,页面置换的次数为6。
访问页面 | 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 | |||||||||||||
缺页否 | √ | √ | √ | √ | √ | √ | √ | √ |
(二) 先进先出(FIFO)
优先淘汰最早进入内存的页面,亦即在内存中驻留时间最久的页面。该算法实现简单,只需把调入内存的页面根据先后次序链接成队列,设置一个指针总指向最早的页面。但该算法与进程实际运行时的规律不适应,因为在进程中,有的页面经常被访问。
访问页面 | 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 | 4 | 4 | 4 | 0 | 0 | 0 | 7 | 7 | 7 | |||||
物理块2 | 0 | 0 | 0 | 3 | 3 | 3 | 2 | 2 | 2 | 1 | 1 | 1 | 0 | 0 | ||||||
物理块3 | 1 | 1 | 1 | 0 | 0 | 0 | 3 | 3 | 3 | 2 | 2 | 2 | 1 | |||||||
缺页否 | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ |
这里仍用上面的实例,釆用FIFO算法进行页面置换。进程访问页面2时,把最早进入内存的页面7换出。然后访问页面3时,再把2, 0, 1中最先进入内存的页换出。由图 3-27可以看出,利用FIFO算法时进行了 12次页面置换,比最佳置换算法正好多一倍。
FIFO算法还会产生当所分配的物理块数增大而页故障数不减反增的异常现象,这是由 Belady于1969年发现,故称为Belady异常,如图3-28所示。只有FIFO算法可能出现Belady 异常,而LRU和OPT算法永远不会出现Belady异常。
访问页面 | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
物理块1 | 1 | 1 | 1 | 4 | 4 | 4 | 5 | 5 | 5 | |||
物理块2 | 2 | 2 | 2 | 1 | 1 | 1 | 3 | 3 | ||||
物理块3 | 3 | 3 | 3 | 2 | 2 | 2 | 4 | |||||
缺页否 | √ | √ | √ | √ | √ | √ | √ | √ | √ | |||
1 | 1 | 1 | 5 | 5 | 5 | 5 | 4 | 4 | ||||
物理块2* | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 5 | |||
物理块3* | 3 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | ||||
物理块4* | 4 | 4 | 4 | 4 | 3 | 3 | 3 | |||||
缺页否 | √ | √ | √ | √ | √ | √ | √ | √ | √ |
图 3-28 Belady 异常
(三) 最近最久未使用(LRU)
选择最近最长时间未访问过的页面予以淘汰,它认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。
再对上面的实例釆用LRU算法进行页面置换,如图3-29所示。进程第一次对页面2访问时,将最近最久未被访问的页面7置换出去。然后访问页面3时,将最近最久未使用的页面1换出。
访问页面 | 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 | 4 | 4 | 4 | 0 | 1 | 1 | 1 | ||||||||
物理块2 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 3 | 3 | 0 | 0 | |||||||||
物理块3 | 1 | 1 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 7 | ||||||||||
缺页否 | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ |
在图中,前5次置换的情况与最佳置换算法相同,但两种算法并无必然联系。实际上,LRU算法根据各页以前的情况,是“向前看”的,而最佳置换算法则根据各页以后的使用情况,是“向后看”的。
LRU性能较好,但需要寄存器和栈的硬件支持。LRU是堆栈类的算法。理论上可以证明,堆栈类算法不可能出现Belady异常。FIFO算法基于队列实现,不是堆栈类算法。
(四). 时钟(CLOCK)
LRU算法的性能接近于OPT,但是实现起来比较困难,且开销大;FIFO算法实现简单,但性能差。所以操作系统的设计者尝试了很多算法,试图用比较小的开销接近LRU的性能,这类算法都是CLOCK算法的变体。
简单的CLOCK算法是给每一帧关联一个附加位,称为使用位。当某一页首次装入主存时,该帧的使用位设置为1;当该页随后再被访问到时,它的使用位也被置为1。对于页替换算法,用于替换的候选帧集合看做一个循环缓冲区,并且有一个指针与之相关联。当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一帧。每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0;如果在这个过程开始时,缓冲区中所有帧的使用位均为0,则选择遇到的第一个帧替换;如果所有帧的使用位均为1,则指针在缓冲区中完整地循环一周,把所有使用位都置为0,并且停留在最初的位置上,替换该帧中的页。由于该算法循环地检查各页面的情况,故称为CLOCK算法,又称为最近未用(Not Recently Used, NRU)算法。
CLOCK算法的性能比较接近LRU,而通过增加使用的位数目,可以使得CLOCK算法更加高效。在使用位的基础上再增加一个修改位,则得到改进型的CLOCK置换算法。这样,每一帧都处于以下四种情况之一:
- 最近未被访问,也未被修改(u=0, m=0)。
- 最近被访问,但未被修改(u=1, m=0)。
- 最近未被访问,但被修改(u=0, m=1)。
- 最近被访问,被修改(u=1, m=1)。
算法执行如下操作步骤:
- 从指针的当前位置开始,扫描帧缓冲区。在这次扫描过程中,对使用位不做任何修改。选择遇到的第一个帧(u=0, m=0)用于替换。
- 如果第1)步失败,则重新扫描,查找(u=0, m=1)的帧。选择遇到的第一个这样的帧用于替换。在这个扫描过程中,对每个跳过的帧,把它的使用位设置成0。
- 如果第2)步失败,指针将回到它的最初位置,并且集合中所有帧的使用位均为0。重复第1步,并且如果有必要,重复第2步。这样将可以找到供替换的帧。
改进型的CLOCK算法优于简单CLOCK算法之处在于替换时首选没有变化的页。由于修改过的页在被替换之前必须写回,因而这样做会节省时间。
四 页面分配策略
(一) 驻留集
指请求分页存储管理中给进程分配的物理块的集合。
- 在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小。
- 应该选择一个合适的驻留集大小
- 若驻留集太小,会导致缺页频繁,系统要花大量的时间来处理缺页,实际用于进程推进的时间很少;
- 驻留集太大,会导致多道程序并发度下降,资源利用率降低。
极端情况:
- 若某进程共有100个页面,则该进程的驻留集大小为100时进程可以全部放入内存,运行期间不可能再发生缺页。
- 若驻留集大小为1,则进程运行期间必定会极频繁地缺页
(二) 页面分配、置换策略
固定分配:
- 操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。
即,驻留集大小不变
可变分配:
- 先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。
即,驻留集大小可变
局部置换:发生缺页时只能选进程自己的物理块进行置换。
全局置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程。
(三)固定分配局部置换
- 系统为每个进程分配一定数量的物理块,在整个运行期间都不改变。
- 若进程在运行中发生缺页,则只能从该进程在内存中的页面中选出一页换出,然后聘调入需要的页面。
缺点:
- 很难在刚开始就确定应为每个进程分配多少个物理块才算合理。
采用这种策略的系统可以根据进程大小、优先级、或是根据程序员给出的参数来确定为一个进程分配的内存块数
(四)可变分配全局置换
- 刚开始会为每个进程分配一定数量的物理块。
- 操作系统会保持一个空闲物理块队列。
- 当某进程发生缺页时,从空闲物理块中取出一块分配给该进程;
- 若已无空闲物理块,则可选择一个未锁定的页面换出外存,再将该物理块分配给缺页的进程。
- 未锁定:系统会锁定一些页面,这些页面中的内容不能置换出外存
如:重要的内核数据可以设为“锁定”
采用这种策略时,只要某进程发生缺页,都将获得新的物理块,仅当空闲物理块用完时,系统才选择一个未锁定的页面调出。被选择调出的页可能是系统中任何一个进程中的页,因此这个被选中的进程拥有的物理块会减少,缺页率会增加。
(五)可变分配局部置换
- 刚开始会为每个进程分配一定数量的物理块。
- 当某进程发生缺页时,只允许从该进程自己的物理块中选出一个进行换出外存。
- 如果进程在运行中频繁地缺页,系统会为该进程多分配几个物理块,直至该进程缺页率趋势适当程度;
- 反之,如果进程在运行中缺页率特别低,则可适当减少分配给该进程的物理块。
(六)策略比较
- 可变分配全局置换:只要缺页就给分配新物理块
- 可变分配局部置换:要根据发生缺页的频率来动态地增加或减少进程的物理块
五 调入页面时机
(一)预调页策略
根据局部性原理,一次调入若干个相邻的页面可能比一次调入一个页面更高效。但如果提前调入的频面中大多数都没被访问过,则又是低效的。
主要根据空间局部性,即:如果当前访问了某个内存单元,在之后很有可能会接着访问与其相邻的那些内存单元。
因此可以预测不久之后可能访问到的页面,将它们预先调入内存,但目前预测成功率只有50%左右。故这种策略主要用于进程的首次调入,由程序员指出应该先调入哪些部分。
(二)请求调页策略
- 运行时调入
- 进程在运行期间发现缺页时才将所缺页面调入内存
由这种策略调入的页面一定会被访问到,但由于每次只能调入一页,而每次调页都要磁盘I/O操作,因此I/O开销较大。
六 从何处调页
(一) 磁盘对换区、文件区
磁盘分为两个部分
- 对换区
读/写速度更快,采用连续分配方式 - 文件区
读/写速度更慢,采用离散分配方式
1、系统拥有足够的对换区空间
- 页面的调入、调出都是在内存与对换区之间进行
- 在进程运行前,需将进程相关的数据从文件区复制到对换区,再调入内存。
- 若内存空间不够,则将内存中的某些页面调出到对换区。
- 这样可以保证页面的调入、调出速度很快。
2、系统缺少足够的对换区空间
- 凡是不会被修改的数据都直接从文件区调入,由于这些页面不会被修改,因此换出时不必写回磁盘,下次需要时再从文件区调入即可。
- 对于可能被修改的部分,换出时需写回磁盘对换区,下次需要时再从对换区调入。
3、UNIX方式
运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可从文件区调入。
若被使用过的页面需要换出,则写回对换区,下次需要时从对换区调入。
七 抖动现象
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动(或颠簸)。
产生抖动的主要原因:进程频繁访问的页面数目高于可用的物理块数(分配给进程的物理块不够)
- 为进程分配的物理块太少,会使进程发生抖动现象。
- 为进程分配的物理块太多,又会降低系统整体的并发度,降低某些资源的利用率