虚拟内存分页机制的页面置换

前言

之前简单介绍过虚拟内存是如何与物理内存进行地址映射的: 虚拟内存分页机制的地址映射, 但是仅仅地址映射是不够的, 在地址映射说过会有缺页的情况, 此时就需要操作操作系统将缺少的页加载到内存中. 但是, 如果内存满了怎么办呢? 毕竟虚拟内存一般都要大于物理内存的, 不可能将所有虚拟内存中的内容都加载到物理内存中.

当需要加载虚拟内存中的内容时, 发现物理内存已经没有空闲空间了. 肿么办嘞? 淘汰一个旧页面, 就可以腾出空间来加载新的页面了. 既然涉及到淘汰, 那么淘汰哪一个页面就是一个问题了. 这篇文章就简单介绍一下几个页面置换的算法, 或者说是页面淘汰的算法.

页面置换算法

什么样的页面置换算法是好的呢? 简单说, 就是尽可能的减少缺页中断的次数. 在发生缺页中断的时候, 是要损失一部分性能的.

最优页面置换

如果有4个页面已经加载了, 现在要在它们之间选择一个进行淘汰, 选择哪一个呢? 要想让中断发生的次数最少, 那么马上就要用到的页面是不能被淘汰的, 这么算下来, 就应该淘汰那个在访问序列中最后的页面. 举个例子:

image-20211129222935884

为什么要在最开始先将内存填满, 而不是用到的时候再进行加载呢? 个人认为, 有如下好处

  1. 根据顺序执行的原则, 连续的页很可能马上就要用到. 故可以减少未来缺页的次数
  2. 即使加载的页用不到, 缺页中断的时候被淘汰了也没有带来性能的损失.
  3. 程序启动的时候, 虽然不能占用全部物理内存, 但还是会分配部分起始内存的. 内存空着也是空着, 不如先将程序加载进去

因为例子的局限性, 例子仅用作对置换规则的理解, 不用作各个算法优劣的比较

但是, 别高兴的太早, 这种情况太过于理想化了, 操作系统是无法预知未来的, 因此这个算法并不能在实际中应用. 讲话了, 不能用你提他干啥啊, 虽然不能实际实现, 但可以作为一个评价其他算法的优劣的标准呀, 越接近最优页面置换的就越好.

接下来, 几个实际应用的置换算法将相继登场(因为无法预测未来, 他们都是基于历史访问来判断的):

先进先出

FIFO, 看名字就能看出来, 那个页面先进来, 哪个页面就先淘汰. 还使用相同的序列进行举例:

image-20211129223723049

但是, 注意看我标记tip1的地方, 页面b在上一次访问刚刚被淘汰, 马上就又要用到了, 这这这…

实现

先进先出的方式, 实现起来很简单, 只需要维护一个页面换入的队列即可. 淘汰时从队首取出淘汰, 放入是添加到队尾即可.

Belady 现象

提到了先进先出, 那么就要说一下这个算法反尝试的Belady现象. 不用去找belady这个单词的意思了, 他是首次发现这种显得的前辈名字.

在常识中, 随着物理页容量增大, 那么缺页中断的频率也会下降, 因为可以将更多的页面放入内存中了嘛. 但是, FIFO可能会出现这样一种情况: 随着物理页容量增大, 缺页中断的频率也随之增大.

举个例子: 有这样一个访问序列: [a, b, c, d, a, b, e, a, b, c, d, e]. 此时物理也中数据为空, 依次将页面读入内存. 过程就不说了, 直接说结果:

  • 当物理页数量为3时, 产生9次缺页中断
  • 当物理页数量为4时, 产生10次缺页中断

而这种Belady现象, 只有FIFO有, 其他算法都没有, 我特意造了写访问序列, 想找到其他算法也存在这种现象的情况, 很可惜, 没有找到. 不过已经就这种现象写了论文描述, 有时间尝试这看看.

最近最久未使用(LRU)

这就是大名鼎鼎的LRU了. 需要淘汰的时候, 刚刚被访问过的页面不能被淘汰, 那就淘汰那个已经很久没有被访问过的页面. 再次举例:

image-20211129225503215

有没有发现, 其实LRU是在仿最优算法, 思路就是, 虽然我无法预测未来, 但是过去我是知道的, 又根据程序的局部性原理, 如果一个页刚刚被访问过, 那么他很大概率马上会再次访问. 而已经很久没有被访问的页面, 未来可能也不会访问.

虽然过去不能完全预测未来, 但好在程序的局部性原理又救了我们, 可以说是最优算法的一个近似解了. 但是别高兴的太早, 思路再好也要拿出可行的方案才行.

实现

要找到哪一个页面已经很久没有访问过了, 可以维护一个访问序列的链表, 首部是刚刚被访问的页面, 尾部就是很久都没有被访问的页面. 这样淘汰页面的时候直接取尾部的页面进行淘汰.

要维护这样的一个链表, 就需要在每次访问内存, 从链表中找到这个页面, 将其移动到首部

为了维护这样一个链表, 给每次内存访问都增加了额外的负担, 操作系统占用的时间越久, 留给应用程序的时间响应的就越少. 可以说得不偿失, 开销太大了.

所以, 虽然LRU看上去很美好, 但是没有一个高效的算法来实现它

时钟页面置换(二次机会)

在之前介绍页面的地址映射时说过, 页表中的每一页都存在着一些标志位, 而其中的一个标志位, 标识当前页是否被访问过, 当页面被访问时, 会由硬件负责将此标记为置为1, 注意是由硬件来完成的, 所以效率是很高的.

我们能不能利用这个标志位来实现页面置换呢? 参考LRU算法, 淘汰那个已经很久没有访问过的页面. 如果说, 先将所有访问位置为0, 等到下一次需要淘汰页面的时候, 找到一个访问位仍是0的页面, 说明在这期间这个页面从来没有被访问过不就可以了么

但是, 现实中的内存动辄几个 G, 如果每次都将所有的页面标识位修改一遍, 效率也是很慢的. 为了提高效率, 可以对这个算法再次进行近似. 将所有的页面连接为一个环, 使用一个指针在环上遍历, 每次需要淘汰页面的时候, 指针就开始遍历, 找到第一个访问位为0的页面淘汰, 同时在遍历的期间, 顺便把经过的页面访问位置为0. 这样每次只扫描部分页面, 最差情况扫描所有(将当前置为0, 扫描一圈回来必定拿到0), 故此算法的步骤如下:

  • 若指针当前指向的页面, 访问位为0, 直接淘汰并指向下一页. 否则进入下一步
  • 若当前页访问位为1, 将其改为0并指向下一页, 回到上一步.

对于被访问过的页面, 会在第二次扫描的时候进行淘汰, 算是给了两次机会吧.

还用刚才的序列进行举例:

image-20211130230322351

时钟算法可以说是对LRU的一个可实现的近似解了. 据说在实际应用中, 效率是比较接近LRU的.

增强版时钟算法

在选择页面淘汰的时候, 如果两个页面都不会被访问了, 淘汰一个和淘汰另一个有区别么? 有的, 别忘了, 页面置换的时候, 不光换入, 还有换出的操作, 也就还是会将淘汰页的数据写回磁盘, 当然如果页面没有被修改就不需要写回的操作了, 数据都一样嘛. 怎么知道页面有没有被修改呢? 巧了, 也有一个标记页面修改的标记位.

刚刚的时钟算法用到了标志位中的访问位, 那么如何将修改位也加入到判断标准中, 优先淘汰没有被修改的页面, 就能够提高页面置换的效率了. 两个标志位的话, 就有如下四种情况, 分情况讨论:

  • (访问位1, 修改位0): 最近访问过, 将访问位置为0
  • (访问位1, 修改位1): 最近访问过, 将访问位置为0
  • (访问位0, 修改位0): 最近没有访问且没有修改, 可以直接置换
  • (访问位0, 修改位1): 最近没有访问但修改过, 将修改位置为0, 等待下一轮访问

但是, 但是, 你有没有想过, 如果将标志位中的修改位置为0了, 那么操作系统进行页面置换的时候, 依据什么来判断当前页是否需要执行回写磁盘的操作呢? 所以, 修改位是不能动的. 如果不动修改位, 又如何来实现呢? 那就要增加额外变量来记录了:

  • 第一圈扫描的时候, 淘汰(访问位0, 修改位0)的页面
    • 同时在扫描的过程中将访问位改为0
  • 如果第一圈没找到, 那么第二圈淘汰(访问位0, 修改位1)的页面

也就是说, 和时钟算法对数据的处理规则相同, 唯一不同的是额外增加临时变量记录当前是第几圈, 第一圈淘汰(访问位0, 修改位0)的页面, 第二圈才会淘汰(访问位0, 修改位1)的页面. 也就是将修改过的页面进行降权操作.(当然, 也可以有其他实现方式, 不过大体意思不变) 再次举例:

image-20211202211933783

如此操作, 使得被修改的页面更不容易被换出去, 进而提升页面置换效率. 实现起来和时钟算法相似. 是时钟算法的增强版.

最不常用(LFU)

LFU, 在淘汰的时候, 淘汰使用次数最少的页面. 也是一种依据过去预测未来的思路, 过去使用较多的页, 很可能在未来也会多次访问. LRU的考察纬度是时间, 而LFU的考察纬度是次数. 再次上图:

image-20211201224225808

有这样一种情况, 程序在初始的时候频繁访问页面 A, 等到程序平稳运行了, 就不会再访问页面 A 了. 但是, 因为计数的结果, 页面 A 的访问次数及高, 导致一直没有被淘汰, 长期驻留在内存中. 如何避免这样的问题呢? 其实也很简单, 出现问题是因为增加了时间纬度, 只需要每个一段时间将页面的计数左移一位(除以2), 这种页面的访问次数就会随着时间推移降下来了.

实现

既然淘汰的依据是页面的访问次数, 那么我们就要知道哪个页面访问次数多, 哪个页面访问次数少. 最直观的思路是, 记录每一页的访问次数, 淘汰时找到值最小的就行了. 但是, 有一个与LRU相同的问题, 你如何来实现这个算法? 在每一次访问页面的时候执行计数器加一的操作么? 代价太大.

全局页面置换

前面介绍的几个页面置换算法, 都假设物理内存容量固定且操作系统中只运行一个进程. 但这和实中是有区别的, 操作系统中运行着很多进程, 每个进程被分配到的物理内存容量都是不同的. 亦或者一个进程刚开始运行的时候会在多个页面之间切换访问, 而随着运行平稳之后访问的页面集中在其中的几个.

这里考虑的是, 如何来确定给不同进程分配的物理内存大小以使得总体的缺页中断率较低. 甚至在进程的不同阶段分配不同大小的物理内存.

全局页面置换算法又有好多, 这里简单提几个, 就不展开说了

工作集页面置换

工作集就是进程在最近 t个时刻所访问过的页面. 其运行规则如下:

  • 页面淘汰时, 有限淘汰不再工作集中的
  • 若都在工作集中, 可通过上述的页面置换处理
  • 若页面已经不在工作集中, 会进行释放
    • 这里与前面的算法产生差异了, 即使没有发生缺页中断, 也会进行页面的释放
    • 从而可以将空闲内存交给其他进程使用

缺页率页面置换

基于工作集页面置换的思想, 当缺页率变大时增加工作集大小, 以使得更多的页面放到内存中. 当缺页率变小时减小工作集大小, 以使得页面得到释放, 提高内存整体利用率.

不过需要计算缺页率的原因, 会导致额外开销的增加.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508362.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes各个组件的概念

前言 Kubernetes中的概念太多了, 什么Pod Service Deployment 等等等等, 给刚接触的我都整蒙了. 通过几天观察下来, 说一下我对各个组件的理解. 此文章仅仅对这些概念做一个简单的介绍, 不至于后面看其他文章的时候一头雾水. Node Node很好理解. 就是服务实际运行的实例, 可…

Kubernetes中Pod生命周期

在 Kubernetes中Pod是容器管理的最小单位, 有着各种各样的Pod管理器. 那么一个Pod从启动到释放, 在这期间经历了哪些过程呢? Pod自开始创建, 到正常运行, 再到释放, 其时间跨度及经历的阶段大致如下: 说一下各个阶段的作用以及是为了解决什么问题. 容器调度和下载镜像的过程就…

wait函数的作用

前言 在编写C程序的时候, 通过fork函数来创建新的进程, wait函数来等待子进程结束. 那么就有一个问题了, 什么情况下父进程需要等待子进程结束后继续执行呢? 如果需要等待子进程结束, 那直接将操作放到父进程执行不就醒了么? 反正等着也是等着. 当然, 还有有一种情况, 任务…

OAuth1.0介绍

背景 为什么需要OAuth授权呢? 最典型的应用场景就是第三方登录了, 我们开发了一个网站希望用户可以QQ登录, 但是怎么能拿到用户的 QQ 信息呢? 用户将 账号密码告诉我们当然可以, 但是这样有如下隐患: 我们拿到了用户的密码, 这样很不安全. 而且任意一个应用被黑, 所有相关…

PHP 数组的内部实现

前言 这几天在翻github的时候, 碰巧看到了php的源码, 就 down 下来随便翻了翻. 地址: https://github.com/php/php-src 那么PHP中什么玩意最引人注目嘞? 一定是数组了, PHP中的数组太强大了, 于是就想着不如进去看看数组的实现部分. 这篇文章打算全程针对代码进行解读了. 以…

base64编码原理

引出 众所周知, ASICC编码共127个, 使用了7个bit进行编码. 而文件在存储的时候是以 字节为单位, 也就是8bit. 这就难免导致有一部分编码是没有定义在ASICC编码中的. 而在网络中传输二进制数据的时候(字符串本质上也是二进制数据嘛), 如果直接传输比特流, 倒也不是不可以, 只是…

页面加载速度-合并资源文件

前言 一直觉得自己的博客站点页面加载很慢, 就想着去优化一下. 呐, 下图是一次文章页面的加载, 需要2.5s. 其中 js 文件就有18个. 众所周知, 浏览器对资源文件的并行下载数量是有限制的(不同浏览器限制不同). 也就是说, 这18个 js 文件是无法同时下载的, 再说了, 页面中还有其…

hbase/thrift/go连接失败

问题 在通过Go连接hbase的过程中, 发现 get操作可以查到数据, 但是scanner命令访问数据失败, 也没有报错, 就是单纯的查不到数据. 而且Python PHP都一切正常. 这里简单复述一下我出现问题的情况, 安装过程和网上大部分内容一致, 这里简单列一下, 只是为了查询问题时参考安装过…

printf缓冲区踩坑

问题 碰到了这样一段代码(经过简化的): #include "stdio.h" #include "unistd.h" #include "sys/wait.h"int main(){fork();printf("1\n");fork();printf("1\n");wait(NULL);return 0; }这里我们简单算一下, 结果会打印几…

进程切换时是如何保存上下文的

前言 当前操作系统大部分采用分时的进程调度, 既每个进程运行一小段时间, 然后切换到下一个进程运行, 依次往复. 当进程运行的时候是独占CPU的, 此时操作系统是无法强行介入的, 为了将执行权让出来, 就需要硬件的配合了. 硬件每个一个时钟周期(比如10ms), 就会产生一个时钟中…

GO/testing包

前言 之前在写GO单元测试的时候, 使用了这个结构testing.T. 进来无事翻了翻, 发现testing包中还有一些其他的结构体, 想来是不同用处. 没想到GO的testing包竟然默默做了这么多支持, 之前竟然不知道. 在testing包中包含一下结构体: testing.T: 这就是我们平常使用的单元测试t…

CPU的分支预测

前言 最近在进行性能调优的时候, 碰到了这样的一段代码(为了展示问题而简化的代码): <?php // 第一次运行 $start microtime(true); for ($i 0; $i < 100; $i) {for ($j 0; $j <1000; $j) {for ($k 0;$k < 10000; $k) {}} } $end microtime(true); echo fi…

PHP获取Opcode及C源码

是什么 在开始之前, 必须要先介绍一下Opcode是什么. 众所周知, Java在执行的时候, 会将.java后缀的文件预先编译为.class字节码文件, JVM加载字节码文件进行解释执行. 而字节码文件存在的意义, 就是为了加速执行. 那么PHP的Opcode与之类似, 也是从.php文件到执行的过程中, 所…

PHP require/include 区别

前言 在PHP中, 载入文件可以选择使用require, 也可以使用include, 那么那他们有什么区别呢? 看了网上的一些文章, 说他们使用场景不同, require一般在文件开头引入文件, include一般在函数中动态引入文件. 但是我觉得并不是这么简单, require是作为语言结构(关键字)出现的, …

Golang 接口原理

问题 小提示, 若想直接查看原理, 可从接口原理开始查看. 有这样一段GO代码: func main() {var obj interface{}fmt.Printf("obj nil. %b\n", obj nil)type st struct{}var s *stobj sfmt.Printf("s nil. %b\n", s nil)fmt.Printf("obj nil. …

三星识别文字_比亚迪电子助力三星Galaxy Note 10系列霸气首发!

三星有子初长成气宇轩昂 秀美俊逸减之一分则嫌柔增之一分则嫌赘2019年8月7日于纽约巴克莱发布Galaxy Note 10系列用简约 重构美三星Galaxy Note 10与Galaxy Note 10分别搭载了6.3英寸和6.8英寸的超感官全视曲面屏&#xff0c;均采用单摄挖孔屏&#xff0c;开孔位于屏幕正上方。…

lisp 设计盘形齿轮铣刀_机械设计基础——周转轮系传动比的计算

点击上方蓝色字体&#xff0c;关注我们15(视频来源于网络&#xff0c;仅供学习交流&#xff0c;侵权请联系删除)机械计重点学习指导机械原理全书重点提要轴的结构改错机械设计作业集01机械设计作业集02机械设计作业集答案机械原理作业集机械原理作业集答案轴的强度计算院校推荐…

b+树阶怎么确定_B站公布年度弹幕,这个排名我不太服气

也忘记了是从什么时候开始&#xff0c;B站开始公布自己的年度弹幕了&#xff0c;今年的年度弹幕排名前五的分别是&#xff1a;爷青回、武汉加油、有内味了、双厨狂喜、禁止套娃。话说今年真的是不容易啊&#xff0c;过年那段时间以及上半年不会忘记那一幕幕感人深邃的瞬间&…

css打印适应纸张_从生态平衡到打印机故障分析

生态平衡(ecological equilibrium)是指在一定时间内生态系统中的生物和环境之间、生物各个种群之间&#xff0c;通过能量流动、物质循环和信息传递&#xff0c;使它们相互之间达到高度适应、协调和统一的状态。也就是说当生态系统处于平衡状态时&#xff0c;系统内各组成成分之…

html5调用系统声音1s响一次_20款奔驰GLC260提车改柏林之声音响,音乐诉请,为爱发声!...

奔驰GLC车型在2020上半年可谓是风生水起&#xff0c;尤其是2020年1-5月份的豪华品牌SUV排名中&#xff0c;奔驰GLC车型以58982的销售量遥遥领先&#xff0c;同比增长了2%&#xff0c;奔驰GLC5月销量高达15275辆&#xff0c;再次打败老对手奥迪Q5L&#xff0c;夺得豪华SUV销量冠…