当前位置: 首页 > news >正文

操作系统导论——第22章 超越物理内存:策略

        在虚拟内存管理程序中,如果拥有大量空闲内存,操作就会变得很容易。页错误发生在空闲页列表中找到空闲页将它分配给不在内存中的页

         内存不够时,这种情况下,由于内存压力(memory pressure)迫使操作系统换出(paging out)一些页为常用的页腾出空间。确定要踢出(evict) 哪个页(或哪些页)封装在操作系统的替换策略(replacement policy)中。

                                                关键问题:如何决定踢出哪个页

        操作系统如何决定从内存中踢出哪一页(或哪几页)?这个决定由系统的替换策略做出,替换策略通常会遵循一些通用的原则(下面将会讨论),但也会包括一些调整,以避免特殊情况下的行为。

 一、缓存管理

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

         知道了缓存命中未命中的次数,就可以计算程序的平均内存访问时间(Average Memory Access Time,AMAT,计算机架构师衡量硬件缓存的指标)。具体来说,给定这些值,可以按照如下公式计算 AMAT:

        例如,假设有一个机器有小型地址空间:4KB(2^12),每页 256 (2^8)字节。因此,虚拟地址由两部分组成:一个4 位VPN(最高有效位)和一个8位偏移量(最低有效位)。因此,本例中的一个进程可以访问总共 2^4 =16 个虚拟页。在这个例子中,该进程将产生以下内存引用(即 虚拟地址)0x000,0x100,0x200,0x300,0x400,0x500,0x600,0x700,0x800,0x900。这些虚拟地址指向地址空间中前10页的每一页的第一个字节(页号是每个虚拟地址的第一个十六进制数字,第一位指向VPN,后两位00指向偏移量,即为第一个字节)。

        进一步假设,除了虚拟页 3 之外,所有页都已经在内存中。因此,我们的内存引用序列将遇到以下行为:命中,命中,命中,未命中,命中,命中,命中,命中,命中。 我们可以计算命中率(hit rate,在内存中找到引用的百分比):90%(PHit = 0.9),因为 10 个引用中有9个在内存中。未命中率(miss rate)显然是10%(PMiss = 0.1)。

        要计算 AMAT,需要知道访问内存的成本访问磁盘的成本。假设访问内存(TM)的成本约为100ns,并且访问磁盘(TD)的成本大约为10ms,则我们有以下AMAT:0.9×100ns + 0.1×10ms,即90ns + 1ms 或 1.0009ms,或约 1ms。如果我们的命中率是99.9%(PMiss = 0.001), 结果是完全不同的:AMAT是10.1μs,大约快100倍。当命中率接近100%时,AMAT接近 100ns。

         在现代系统中,磁盘访问的成本非常高,即使很小概率的未命中也会拉低正在运行的程序的总体 AMAT。显然,必须尽可能地避免缓存未命中,避免程序以磁盘速度运行。

二、最优替换策略

        最优替换策略能达到总体未命中数量最少:即替换内存中在最远将来才会被访问到的页,可以达到缓存未命中率最低。

        追踪一个简单的例子,来理解最优策略的决定。假设一个程序按照以下顺序访问虚拟页:0,1,2,0,1,3,0,3,1,2,1。表 22.1展示了最优的策略,这里假设缓存可以存 3 个页。 

         在表22.1 中,可以看到以下操作。前 3 个访问是未命中,因为缓存开始是空的。这种未命中称作冷启动未命中(cold-start miss,或强制未命中,compulsory miss)。然后引用页 0 和 1,它们都在缓存中。最后,又有一个缓存未命中(页 3),但缓存已满,必须进行替换!使用最优策略,检查当前缓存中每个页(0、1和2)未来访问情况,可以看到页0马上被访问,页1稍后被访问,页2在最远的将来被访问。因此,最优策略的选择很简单:踢出页面2,结果是缓存中的页面是0、1和3。接下来的3个引用是命中的,然后又访问到被我们之前踢出的页2,那么又有一个未命中。这里,最优策略再次检查缓存页(0、1和3)中每个页面的未来被访问情况,并且看到只要不踢出页1(即将被访问)就可以。这个例子显示了页3被踢出,虽然踢出0也是可以的。最后,我们命中页1,追踪完成。

 

         然而,未来的访问是无法知道的,无法为通用操作系统实现最优策略。

三、简单策略:FIFO(先入先出)

        一些系统使用FIFO(先入先出)替换策略。页在进入系统时,简单地放入一个队列。当发生替换时,队列尾部的页(“先入”页)被踢出。FIFO有一个很大的优势:实现相当简单

 

四、简单策略:随机

         在内存满的时候它随机选择一个页进行替换。随机具有类似于 FIFO 的属性。实现起来很简单,但是它在挑选替换哪个页时不够智能。随机策略在上述例子的引用流程。

 

 的次数是6次。有时候情况会更糟糕,只有2次或更少。随机策略取决于当时的运气。

 五、利用历史数据:LRU

        FIFO 和 随机 这样的简单策略都有一个共同的问题:可能会踢出一个重要的页,而这个页马上要被引用。为了提高后续的命中率,通过历史的访问情况作为参考。例如,如果某个程序在过去访问过某个页,则很有可能在不久的将来会再次访问该页。

         页替换策略可以使用的一个历史信息是频率(frequency)。如果一个页被访问了很多次, 也许它不应该被替换,因为它显然更有价值。页更常用的属性是访问的近期性(recency)越近被访问过的页,也许再次访问的可能性也就越大。

         局部性原则,基本上只是对程序及其行为的观察。原理上就是程序倾向于频繁地访问某些代码(例如 循环)和数据结构(例如循环访问的数组)。因此,一系列简单的基于历史的算法“最不经常使用 LFU ” 策略会替换最不经常使用的页。“ 最少最近使用 LRU” 策略替换最近最少使用的页

       LRU 如何在示例引用序列上执行。表22.4展示了结果。从表中,可以看到 LRU 如何利用历史记录,比无状态策略(如随机或FIFO)做得更好。在这个例子中,当第一次需要替换页时,LRU会踢出页 2,因为 0 和 1 的访问时间更近。然后它替换页 0,因为 1 和 3 最近被访问过。在这两种情况下,基于历史的LRU的决定证明是更准确的,并且下一个引用也是命中。

 

 六、工作负载示例

        查看更复杂的工作负载(workload),而不是追踪小例子。但是,这些工作负载也被大大简化了。更好的研究应该包含应用程序追踪。

        1. 没有局部性

        第一个工作负载没有局部性,这意味着每个引用都是访问一个随机页。在这个简单的例子中,工作负载每次访问独立的 100 个页,随机选择下一个要引用的页。总体来说,访问了10000个页。在实验中,我们将缓存大小从非常小(1页)变化到足以容纳所有页(100 页),以便了解每个策略在缓存大小范围内的表现。

        图22.2 展示了最优、LRU、随机和FIFO策略的实验结果。图22.2中的y轴显示了每个策略的命中率。如上所述,x轴表示缓存大小的变化。 

        可以得出一些结论:当工作负载不存在局部性时,使用的策略区别不大,LRU、FIFO 和随机都执行相同的操作,命中率完全由缓存的大小决定。其次,当缓存足够大到可以容纳所有的数据时,使用哪种策略都无关紧要,都有 100% 的命中率。最后,最优策略明显好于实际的策略。

         2. 80—20 负载

        表现出局部性:80%的引用是访问20%的页(“热门”页)。剩下的20%是对剩余的80%的页(“冷门”页)访问。在我们的负载场景,总共有100个不同的页。因此,“热门”页是大部分时间访问的页,其余时间访问的是“冷门”页。图22.3展示了不同策略在这个工作负载下的表现

 (降低未命中率)也会对性能产生巨大的影响。如果未命中的代价不那么大,那么 LRU 带来的好处就不会那么重要。

        3. 循环顺序工作负载 

        其中依次引用 50 个页,从0开始,然后是1,…,49,然后循环,重复访问,总共有10000次访问50个单独页。图22.4 展示了这个工作负载下各个策略的行为。

七、实现基于历史信息的算法

        以 LRU 为例。为了实现它,需要做很多工作。具体地说,在每次页访问(即每次内存访问,不管是取指令还是加载指令还是存储指令)时,都必须更新一些数据,从而将该页移动到列表的前面(即MRU侧)。与 FIFO 相比,FIFO 的页列表仅在页被踢出(通过移除最先进入的页)或者当新页添加到列表(已到列表尾部)时才被访问。为了记录哪些页是最少和最近被使用,系统必须对每次内存引用做一些记录工作。显然,如果不十分小心,这样的记录反而会极大地影响性能。

        有一种方法有助于加快速度,就是增加一点硬件支持。例如,硬件可以在每个页访问时更新内存中的时间字段(时间字段可以在每个进程的页表中,或者在内存的某个单独的数组中,每个物理页有一个)。因此,当页被访问时,时间字段将被硬件设置为当前时间。 然后,在需要替换页时,操作系统可以简单地扫描系统中所有页的时间字段以找到最近最少使用的页。  

        遗憾的是,随着系统中页数量的增长,扫描所有页的时间字段只是为了找到最精确最少使用的页,这个代价太昂贵。想象一下一台拥有4GB内存的机器,内存切成4KB的页。 这台机器有一百万页,即使以 现代CPU 速度找到 LRU页 也将需要很长时间。这就引出了一个问题:我们是否真的需要找到绝对最旧的页来替换?找到差不多最旧的页可以吗

八、近似 LRU

        从计算开销的角度来看,近似LRU更为可行,实际上这也是许多现代系统的做法。需要硬件增加一个使用位(use bit,有时称为引用位, reference bit),这种做法在第一个支持分页的系统Atlas one-level store 中实现。系统的每个页有一个使用位,然后这些使用位存储在某个地方(例如,它们可能在每个进程的页表中,或者只在某个数组中)。每当页被引用(即读或写)时硬件将使用位设置为1。 但是,硬件不会清除该位(即将其设置为0),这由操作系统负责。

         操作系统如何利用使用位来实现近似 LRU?时钟算法(clock algorithm)系统中的所有页都放在一个循环列表中。时钟指针开始时指向某个特定的页。当必须进行页替换时,操作系统检查当前页的使用位是 1 还是 0。如果是 1,则意味着页面 P 最近被使用,因此不适合被替换。 然后,P 的使用位设置为0,时钟指针递增到下一页(P + 1)。该算法一直持续到找到一个使用位为0 的页,使用位为0意味着这个页最近没有被使用过(在最坏的情况下,所有的页都已经被使用了,那么就将所有页的使用位都设置为 0

         

九、脏页

         时钟算法的一个小修改,是对内存中的页是否被修改的额外考虑。这样做的原因:如果页已被修改并因此变脏,则踢出它就必须将它写回磁盘,这很昂贵。如果没有被修改(为干净的),踢出就没成本。物理帧可以简单地重用于其他目的而无须额外的I/O。因此,一些虚拟机系统更倾向于踢出干净页,而不是脏页。

        为了支持这种行为,硬件应该包括一个修改位(modified bit,又名脏位,dirty bit)。每次写入页时都会设置此位,因此可以将其合并到页面替换算法中。例如,时钟算法可以被改变,以扫描既未使用又干净的页先踢出。无法找到这种页时,再查找脏的未使用页面,等等。 

十、其他虚拟内存策略

        页面替换不是虚拟内存子系统采用的唯一策略(尽管它可能是最重要的)。例如,操作系统还必须决定何时将页载入内存。该策略有时称为页选择(page selection)策略,它向操作系统提供了一些不同的选项

         对于大多数页而言,操作系统只是按需分页,这意味着操作系统在页被访问时将页载入内存中,“按需即可”。当然,操作系统可能会猜测一个页面即将被使用,从而提取载入。这种行为称为“预取”,只有在合理的成功机会时才应该这样做。例如,一些系统将假设如果代码页 P 被载入内存,那么代码页 P+1 很可能很快被访问,因此也应该被载入内存。

        另一个策略决定了操作系统如何将页面写入磁盘。当然,它们可以简单地一次写出一个。然而,许多系统会在内存中收集一些待完成写入,并以一种(更高效)的写入方式将它们写入硬盘。这种行为被称为聚集写入,或者是分组写入,这样做有效是因为硬盘驱动器的性质,执行单次大的写操作,比许多小的写操作更有效。 

十一、抖动

         当内存被超额请求时,操作系统应该做什么,这组正在运行的进程的内存需求是否超出了可用物理内存?在这种情况下,系统将不断地进行换页,这种情况有时被称为抖动

         一些早期的操作系统有一组相当复杂的机制,以便在抖动发生时检测并应对。例如,给定一组进程,系统可以决定不运行部分进程,希望减少的进程工作集(它们活跃使用的页面)能放入内存,从而能够取得进展。这种方法通常被称为准入控制(admission control)。

         目前的一些系统采用更严格的方法处理内存过载。例如,当内存超额请求时,某些版本的Linux 会运行“内存不足的杀手程序(out-of-memory killer)”。这个守护进程选择一个内存密集型进程并杀死它,从而以不怎么委婉的方式减少内存。虽然成功地减轻了内存压力,但这种方法可能会遇到问题,例如,如果它杀死 X 服务器,就会导致所有需要显示的应用程序不可用。

相关文章:

  • 基于x86/RK3568电力新能源智能变电站一体化装置
  • CMS 垃圾收集器深度解析
  • 《计算机视觉度量:从特征描述到深度学习》—生成式人工智能在工业检测的应用
  • ceph scrub 导致业务问题优化
  • 【Dify(v1.2) 核心源码深入解析】Agent 模块
  • 深入讲解 CSS 选择器权重及实战
  • 【刷题2025】单指针双指针+滑动窗口+二分法三分法+区间问题
  • 如何一键检查网页里的失效链接和废弃域名?
  • 【加密算法】SM2密钥生成与转换详解:从原理到代码实现
  • ecovadis分为哪些类别,要进行ecovadis认证有什么要求
  • 榕壹云场馆预定系统:基于ThinkPHP+MySQL+UniApp打造的全能运动馆智慧运营解决方案
  • 解锁Grok-3的极致潜能:高阶应用与创新实践
  • 多模态大模型文字识别 vs OCR识别模型
  • 【Python进阶】断言(assert)的十大核心应用场景解析
  • RelativeLayout(相对布局)
  • Mac电脑交叉编译iphone设备可以运行的redsocks, openssl, libsevent
  • Rust + WebAssembly 性能剖析指南
  • 辛格迪客户案例 | 厦门三维丝实施SAP系统
  • js ES6箭头函数的作用
  • 0415-批量删除操作
  • 医改经验如何因地制宜再创新?国家卫健委“以例说法”
  • 夜读丨一条鱼的使命
  • 2025航天文化艺术论坛在上海举办
  • 大家聊中国式现代化|刘亮:因地制宜发展新质生产力,推动经济高质量发展
  • 神二十6个半小时到站
  • 智飞生物一季度营收下滑79%,连续三个季度亏损,称业绩波动与行业整体趋势一致