虚拟内存
引入:
程序加载进入内存中有两种方式:(1) 程序 ==完整== 地加载进入内存 (2) 仅加载 ==部分== 程序,剩余部分在需要时再进行加载
解释 1:此前提到的 ==分页技术== 都是在内存中为程序的每页都分配相应的帧:相当于将程序完整地加载进入内存中
解释 2:此前提到的 ==动态加载== 就是加载部分程序,需要时再加载;即将提到的 ==虚拟内存== 基本原理也是如此
完整地加载整个程序会产生许多问题:(1) 程序的大小受限于物理内存的大小 (2) 多道程序系统的度受到限制
解释 1:游戏等大型软件就不可能制作超过百G大小,因为不可能有这么大的内存(除非你插很多的内存条)来完整地存放这个游戏
解释 2:物理内存为 16G 的个人计算机最多只能够容纳 16个 1G 大小的软件(操作系统通常还要占据 1G~4G),如果软件更大,显然容纳的个数就越少
问题解决方式:(1) 动态加载 (2) 虚拟内存
概述
定义:利用 ==按需调页== 和 ==页面置换== 技术在 ==逻辑上== 对物理内存进行 ==扩大== 的技术
核心:按需调页 + 页面置换
解释:
程序在加载进入内存中时,仅将程序的 需要使用的页 装入内存,而将 暂时不需要使用的页留在后备存储中
处理器再执行程序中的指令时,访问的部分没有被加载进入内存中,此时将这部分程序加载进入内存中 -> 按需调页
空闲内存低于阈值时,会将内存中 暂时没有使用页换出 到后备存储的 交换空间 中,换入 当前处理器 需要访问的页 -> 页面置换
因为不断地换入换出进程的页面,用户就会感觉进程 整个 都在内存中运行,就好像使用了比实际物理内存更大内存,也就是虚拟内存
举例:在线观看视频
浏览器并不会将整个视频全部传输完成再播放,那样显然就变成下载了;
浏览器每播放一部分就下载一部分,播放过的部分就会被正在播放的部分直接覆盖;
用户需要倒推观看时会发现仍然需要缓冲(除非网速很快),因为之前观看的部分已经被删除了;
这样给用户的感觉就是整个视频都被加载到内存中播放,但实际上仅仅用了很少的内存
特点:
实现:
- 基于 ==分页式== 的内存管理
- 基于 ==分段式== 的内存管理
- 基于 ==段页式== 的内存管理
细节:
虚拟内存称呼:
虚拟内存技术:内存管理的一种技术,并不是指的某个具体存储空间
虚拟内存空间(交换空间):硬盘等外部存储设备利用部分存储空间存储临时性的虚拟地址,这里指的就是实际存在的存储空间,也被称为交换空间
注:这两个概念通常都被简称为虚拟内存,实际代表什么需要根据上下文语境来查看
虚拟内存查看:
windows: systeminfo 指令可以查看虚拟内存情况

请求调页
概述
-
注:按需调页也被称为 ==惰性交换==
调页器:
定义:负责将进程的 页面 ==换出== 和 ==换入== 内存
过程:
- 调页器 猜测 进程的运行可能需要哪些相关的页
- 调页器 调入 进程正常运行可能需要的相关页(页表和帧表都会相应的更新)
- 调页器 设置 被调入页的 ==有效-无效位== 为 有效
细节:调度器和调度程序执行的工作是相同的 -> 在讨论进程调度时通常使用调度程序这个较大的概念,在讨论请求调页时通常使用调页器这个较小的概念
注:暂时不清楚在实际的操作系统中是如何实现的
优点 & 缺点:
- 优点:减少 ==交换时间== 和所需要使用的 ==物理内存空间==
- 缺点:产生 页错误

页错
-
注:不能够说进程试图访问有效-无效位为无效的页面时产生的错误:==没有被调入内存 ≠ 有效-无效位为无效==
分类:
进程访问的页确实是无效的 -> 系统就没计划为其分配内存,产生页错
进程访问的页面是有效的 -> 系统计划为其分配内存但是并没有真正分配,产生页错
解释:这种情况的出现主要是由于程序员声明了分配内存,但是暂时还没有使用这块内存,所以系统不会为其真正分配内存
==处理==:
操作系统执行 陷阱处理 程序
调度程序 保存进程上下文(进程的程序计数器,进程状态…)
开始处理中断:判断中断是否为 缺页中断:
(1) 如果是缺页中断: 调入相应的页进入空闲帧
注:如果帧表中存在空闲帧,那么直接调入就行;如果帧表中不存在空闲帧,那么需要使用 页面置换,换出暂不使用的页,换入需要使用的页
(2) 如果是越界中断:直接终止进程
I/O 系统控制磁盘读取相应的页:
(1) 调度程序执行上下文切换:进程进入设备队列等待(
running
->waiting
)(2) 等待磁盘的的寻道时间(查询数据的时间)
(3) 磁盘查询到相应数据开始传输数据
调度程序将处理器 分配给其余需要使用的进程
I/O 系统在磁盘传输完成后 发出中断
调度程序 保存进程上下文
确定中断来自于磁盘并且开始处理中断
更新页表 及其相关信息 (
waiting
->ready
)调度程序再次执行上下文切换
重新将处理器分配给该进程并且 重新执行指令
性能分析:
处理页错是十分花费时间和消耗性能的事情
页错误处理时间:处理缺页中断 + 载入页面 + 重新启动进程
注:使用 I/O 载入页面时很有可能遇上等待,有时候不能只考虑 I/O 的读取时间,还要考虑等待时间
特殊情况:页错误概率 = 0:代表当前进程的所有页都被加载进内存了;页错误概率 = 1:代表当前进程没有页被加载进内存、
细节:
纯请求调页:
定义:调页器在处理器执行指令前 ==不加载任何页面== 进入内存,等到处理器开始执行指令发现需要页面不存在时,调页器才开始调用需要的页面
特点:从一开始就会发生页错,并且会持续发生页错,直到进程需要的页面全部存在于内存中
解释:这是一种非常极端的情况,应该没有操作系统会这样去实现
交换空间:
定义:磁盘上一块 ==存储被调出页面== 的连续空间
使用方式:
程序被载入内存前:交换空间中存储程序的 ==所有页面==,后续的 ==读取页面== 和 ==调度页面== 都在 ==交换空间== 中进行
程序被载入内存前:交换空间中仅存储被从内存中 ==调出的页面==,系统从 ==文件系统中读取页面==,从 ==交换空间中调入页面==
解释:处理页错误时,如果交换空间存在需要的页面那就从交换空间中获取;如果交换空间没有就说明这个页面没有加载过,显然从文件中获取
交换空间也被称为虚拟内存空间/匿名内存:因为这块磁盘上的空间存储的都是从内存中调出的页面,所以相当于是内存的扩展,但并不属于内存
解释:运行大型游戏的时候,有时候会发现在运行期间C盘会变小,变小的那部分空间就被用作虚拟内存使用了,系统希望为游戏的运行腾出更多空间
页面置换
概述
前提:内存中已经 没有空闲帧 用于存放新加载进入内存的页面
- 解决方式:(1) 终止当前需要调页的进程 (2) 换出 其他进程释放空间 (3) 页面置换
定义:
- 调度程序选择 ==牺牲帧== 并将其 ==换出== 到后备存储的 ==交换空间== 中
- 调度程序再将 需要使用的帧 换入(加载)进入内存的 ==空闲帧== 中
分类:
局部替换策略:仅允许进程从 ==属于自己的物理帧== 中选择替换
全局替换策略:允许进程可以从 ==所有的物理帧== 中选择替换
处理过程:
内存管理单元根据 页表 确定需要使用的 页面的磁盘地址
调度程序寻找 空闲帧
内存中存在空闲帧:随机选择某个空闲帧存放页面(优先)
内存中不存在空闲帧:利用 页面置换算法 选择 牺牲帧 将其换出到 交换空间 中腾出空闲帧存放页面
牺牲帧并不一定需要换出到交换空间:只要牺牲帧没有被修改过就不需要
内存管理单元 修改帧表和页表
从发生缺页错误的位置继续执行进程
修改位(脏位)
定义:用于标记页面是否被 ==修改== 过
功能:用于 ==减少== 页面置换时产生的换入换出 ==开销==
解释:如果页面没有被修改过,那么就不需要再将其换出到交换空间中,因为和磁盘存储的页面是一样的;如果页面修改过,就需要将其换出到交换空间中
细节:
置换算法
前提:下列所有算法都是 基于局部替换策略
内存引用串:
定义:处理器一段时间内需要访问的 页号序列
举例:逻辑地址序列 101 114 514 223 233 页面大小 100 -> 内存引用串:1 1 5 2 2
计算方式:
10 进制逻辑地址:逻辑地址 ÷ 页面大小 = 页号 + 偏移量
2 进制逻辑地址:
- 方法一:2进制逻辑地址转换为10进制逻辑地址再算
- 方法二:(1) 2进制逻辑地址的比特位个数 - 表示页面大小需要的比特位个数 = 表示页号需要的比特位个数 (2) 再将2进制的页号转为10进制
举例:2进制逻辑地址:10110011 页面大小 15:相当于是最低 4 位表示 ???
算法评判标准:帧错误率尽可能低
FIFO 置换算法
定义:每次选择 ==最早进入== 内存的页面进行替换
实现:利用 队列 记录页面进入内存的“时间”:并不记录具体的时间
解释:最早进入内存的页面就是队首元素,后续进入的页面依次进入队列;
每次页面置换的时候就选择队首元素替换,然后让队列第二个元素称为队首元素,新的页面称为队尾元素
优点:实现最为简单
缺陷:(1) 产生 ==Belady异常==:随着进程被分配的 物理帧数的增加,缺页错误的数量也增多 (2) 性能 最差
OPT 置换算法
- 定义:每次选择那些在 ==未来 长时间不使用== 的页面进行替换
- 优点:具有 最优 的性能
- 缺点:几乎不能实现(解释:不可能预测到哪些页面将会在未来访问)
LRU 置换算法
- 定义:每次选择那些在 ==过去 长时间不使用== 的页面进行替换
- 实现:
- 计数器实现
- 每个页面维护一个计数器
- 每次页面被访问时该页面的计数器就递增
- 堆栈实现
- 堆栈中保存当前 使用过 的页号
- 堆栈中的页号每次被引用时都从栈中移除然后 压入栈顶;不存在于堆栈的页号被引用时 直接压入栈顶
- 确保堆栈的 顶端 是最近访问的页面,堆栈的 底端 是长时间没有访问页面
- 计数器实现
- 细节:
- 两种实现方式都属于 堆栈算法,绝对不会产生 Belady 异常
- 采用这两种实现方式的 LRU 算法也被称作标准 LRU 算法:是不可能实现的
近似 LRU 置换算法
额外引用位算法
每个页面都必须维护一个 ==8位 的移位寄存器==:表示为 $R = R_7R_6···R_2R_1R_0$(表示寄存器的位数)
每次访问某个页面时都将该页面的移位寄存器中的 ==R
7位置为 1==每隔一定的时间移位寄存器中就会 ==右移一位==,最低位直接 ==抛弃==
举例:时钟信号发出,移位寄存器右移,R
3= R4= 1,其余位置一样最后选择 ==寄存器数值最小== 的作为长时间没有访问页面
举例:页面 0 寄存器:11001100; 页面 1 寄存器:00110101 ;页面 2 寄存器 00010111 -> 显然页面 2 是寄存器数值最小的页面
第二次机会算法:
- 每个页面仅维护 ==1位== 的引用位;并且将所有页面以 ==循环队列== 的方式维护
- 每次替换页面时就在循环队列中查找:(1) 如果引用位为 0 就直接替换该页面 (2) 如果引用位为 1 就继续循环查找并将当前页面的引用位置为 0(二次机会)
注:最坏的情况是每页的引用位都是 1:那么该算法将会将所有的引用位重新置为 0 再进行循环查找 (相当于退化成 FIFO 算法)
基于计数的置换算法
- 最不常使用算法
- 最常使用算法