操作系统导论-内存篇4

以上的所有内容都是讨论有足够的内存页,发生页面错误时,从空闲页列表找到空闲页,把他分配给不在内存的页。但是,内存经常是不足的,操作系统被迫的需要换出一些页,为常用页腾出空间。那么操作系统如何决定从内存中踢出哪些页?这个决定是由操作系统的替换策略做出,替换策略通常会遵循一些通用原则,但是也会包含一些调整,以避免特殊情况的发生。

内存只包含系统中所有页的子集,因此可以将其视为操作系统中虚拟内存页的缓存。因此,在为这个缓存选择替换策略时,我们的目标是让缓存未命中(cache miss)最少,使得从磁盘获取页的次数最少。或者,将其看待为让缓存命中(cache hit)最多,即在内存中找到待访问的页面次数最多。

知道了缓存命中和未命中次数,就可以计算程序的平均内存访问时间(average memory access time,AMAT)。具体来说AMAT如下计算公式:


         AMAT= \left ( P_{Hit}\cdot T_{M} \right )+\left ( P_{Miss} \right \cdot T_{D})

例如,一个机器只有一个小型的地址空间:4KB,每页256字节。因此虚拟地址由两部分组成:一个4位的VPN(最高有效位),一个8位的偏移量(最低有效为)。因此,一个进程最多可以访问2^4=16个虚拟页。该例子中一个进程产生以下内存引用:

0x000,0x100,0x200,0x300,0x400,0x500,0x600,0x700,0x800,0x900。

这些虚拟地址指向地址空间前10页的每一页的第一个字节,即页号。

我们再假设只有第三个页没有命中,所以命中率为:90%,未命中率是10%。

再假设内存访问成本(TM)为100ns,磁盘的访问成本(TD)为10ms,那AMAT:0.9*100ns+0.1*10ms,约1ms。

我们可以看到,磁盘的访问成本非常高,即使很小的未命中率,也会大幅度拉低正在运行程序的AMAT,所以,我们要尽量避免缓存未命中。

最优替换策略

为了理解特定的替换策略是如何工作的,我们需要一个最好的替换策略来进行比较。最优替换策略是Belady发明的(叫做MIN)。他可以让整体未命中数量最少。这是一个很简单的方法,当然很难实现,即替换内存中在最远将来才会被访问的页。

虽然最优策略不切实际,但是作为仿真或者其他研究的比较者还是非常有用的。比如,但愿你的算法有80%命中率是没有意义的,但是加上最优算法只有82%命中率,就会使结果非常有意义。因此,在做任何研究时,知道最优策略可以方便进行对比,知道你的策略有多大的改进空间,也用于决定当前策略已经非常接近最优策略时,停止无所谓的优化。

这个思想理解很容易,缓存中所有的页都要比这个页重要,因为在最远的将来才会被访问,那你肯定会先引用其他页。

在计算机体系结构中,架构师会将缓存未命中分为三类:强制性、容量和冲突未命中,有时称为3C。发生强制性未命中是因为缓存开始都是空的,而这是对项目的第一次访问。由于缓存空间不足而不得不移除一些缓存时,就发生了容量未命中。冲突未命中出现在硬件中,因为硬件缓存中对项的放置位置有限制,这是由于所谓的集合关联性。他不会出现在操作系统页面缓存中,因为这样的缓存总是完全关联的,即页面可以放置的内存位置没有限制。

但是未来是不可预知的,所以这种最优策略是无法实现的。

简单策略:FIFO

最早期,一些系统使用的最简单的FIFO替换策略。页在进入系统时,简单的放入一个队列。当发生替换时,队列尾部的页被踢出。FIFO的最大优势:实现简单。

Belady异常:Belady及其同事发现了一个有意思的引用序列。内存引用顺序是:1,2,3,4,1,2,5,1,2,3,4,5.如果采用FIFO替换策略时,缓存大小由3变成4时,命中率反而降低了。

但是一般来说,当缓存变大时,缓存命中率是会提高的。但是,以上这种情况恰好相反,这种现象被称为Belady的异常。

其他的一些策略例如LRU,不会遇到这个问题,这是因为LRU具有所谓的栈特性。对于具有这个特性的算法,大小为N+1的缓存自然包含大小为N的缓存的内容。因此,当增加缓存大小时,缓存命中率至少保持不变,有可能提高。FIFO和随机策略显然没有栈特性,因此容易出现异常行为。

利用历史数据:LRU

像FIFO和随机策略,都会有一个共同问题:他可能会踢出一个重要的页,而这个页马上要被引用。

像调度策略一样,为了提高后续的命中率,我们需要通过历史的访问情况作为参考。页替换策略可以使用的一个历史信息是频率。如果一个页被访问了很多次,也许他不应该被替换,因为他显然更有价值。页更常用的属性是访问的近期性,越近被访问过的页,也许再次访问的可能性就越大。

这一些列的原理就是局部性原理,基本上只是对程序及其行为的观察。这个原理简单来说就是程序倾向于频繁的访问某些代码和数据结构(例如循环)。因此,我们应该尝试用历史数据来确定哪些页更重要,并在需要踢出时把这些页保留在内存。

因此一系列基于历史的简单算法就产生了,LFU(最不经常使用),LRU(最近最少使用)。

程序倾向于表现出两种局部性。第一种是空间局部性,他指出如果页面P被访问,那围绕P的页面也会被访问。第二种是时间局部性,他指出近期访问过的页面很可能在不久再被访问。假设存在这些类型的局部性,对硬件系统的缓存层次结构起着重要作用,硬件系统部署了许多级别的指令、数据和地址转换缓存,以便在存在此类局部性时,能够帮助程序快速运行。

LRU的问题在于访问内存时需要做很多额外的工作,在每次页访问时,需要更新数据和记录哪些页是最近和最少被使用的。有一种方法是硬件支持,例如硬件可以在每个页访问时更新内存中的时间字段。需要替换时,扫描所有页的时间字段,找到最近最少被使用的页。

但是,随着操作系统的页数量增加,扫描所有页的时间字段只为了找到最精确的最少使用的页,代价过于昂贵。那我们可以实现一个近似的LRU算法,并且依然可以达到预期的效果。

从计算开销的角度来看,近似LRU算法可行。我们在硬件增加一个使用位,系统中每个页有一个使用位,然后这些位存在某些地方,每当页被访问时,硬件将使用位设置为1,清除操作由操作系统来做。

操作系统如何实现近似LRU?有一种简单的方法叫做时钟算法。系统中所有页都放在一个循环链表里,时钟指针开始指向某个特定页。必须进行替换时,操作系统检查当前指向的页的使用位是1还是0。如果是1,意味着页面P最近被使用,因此不适合替换。然后,把P的使用位置为0,时钟指针递增指向下一页。该算法一直持续到找到一个使用位为0的页。

时钟算法的一个小修改,是增加了对内存页是否为脏页的额外考虑。这样做的原因是:如果页是脏页,则踢出他必须要回写磁盘,这很昂贵。如果不是脏页,踢出没有成本,物理帧可以直接复用与其他目的不需要额外的IO。因此一些系统更倾向于与踢出干净页,而不是脏页。

其他的一些策略

页面替换不是操作系统唯一的策略,他还需要决定何时将页载入内存,该策略被称为页选择策略。

对于大多数页而言,操作系统只是使用按需分页,这意味着操作系统在页被访问时将页载入内存,按需即可。但是,操作系统一般会猜测一个页面即将被使用,从而提前载入,这种行为称为预取。

另一个策略是决定操作系统何时将页写入磁盘。一般操作系统会收集一些写入,以更高效的方式一起写入。这种行为被称为聚集写入,或者分组写入。

抖动

如果运行的进程的内存需求超过了可用物理内存时,系统将不停的进行换页,这种情况被称为抖动。

早期的操作系统,会有很复杂的机制,以便发生抖动时检测并应对。例如,系统可以决定不运行某些进程,希望减少的进程工作集可以放入内存,取得运行进展。这种方法被称为准入控制,它表明少做工作有时比尝试一下做好所有事情更好。

目前一些操作系统采用更严格方式处理内存过载。例如,内存超额请求时,某些Linux系统会运行”out of memory killer“这个守护进程,会选择一个内存密集型进程杀死他,从而以暴力方式减少内存。

以上,内存篇结束,内容基本都是科班学过的,不过一些思考很受启发。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值