目录
虚拟内存允许执行进程不必完全处于内存
一、背景
虚拟内存是内存管理的一种技术,它允许执行进程时不必完全载入内存
,可以部分程序载入到内存
优点:
- 逻辑地址空间可大于物理地址空间
- 可以被多个进程共享地址空间
- 可以提供更有效的进程创建
虚拟内存
将用户逻辑内存和物理内存分开,这在现有物理内存有限的情况下,为程序员提供了巨大的虚拟内存。
进程的虚拟地址空间就是进程如何在内存中存放的逻辑视图。通常从逻辑地址(如地址0)开始,连续存放。
注意
,上图为虚拟地址空间,上图中,随着动态内存的分配,允许堆向上生长;随着子程序的不断调用,允许堆栈向下生长。
堆与堆栈之间的巨大空白空间为虚拟地址的一部分
,只有在堆与堆栈生长时
,才需要实际的物理页
虚拟内存也允许共享,实现进程之间的内存共享
二、请求调页
仅在需要时加载页面,这种技术称为请求调页
1.基本概念
调度程序需要一定的硬件支持,以区分内存页面和磁盘的页面。有效-无效位
方案可以解决

- 有效(v):页面在内存中
- 无效(i):页面不在进程逻辑地址空间中,或有效但只在磁盘上
如果进程试图访问那些尚未调入内存的页面时,对标记为无效的页面访问会产生缺页错误
处理这种缺页错误的程序很简单:
- 检查进程内部表,已确定该引用是有效的还是无效的
- 如果非法,终止进程;如果有效但尚未调入页面,那么应该调入
- 找到一个空闲帧
- 将所需要的页调入到,找到的空闲帧里
- 修改页表(有效、无效位),以表示该页已在物理内存中
- 重启被陷阱中断的指令(重新访问)
三、写时复制
写时复制
(cow)通过允许父进程和子进程最初共享相同的页面来工作。这些共享页标记为写时复制,这意味着如果任何一个进程需要对页进行写操作,那么就会创建一个共享页的副本。
优点:
- 可以快速创建进程
- 最小化创建新进程的页数
四、页面置换
如果增加了多道程序,那么可能会过度分配内存
内存的过度分配会有问题。当用户进程正在执行时,可能发生页面错误。操作系统确定所需页面的磁盘位置,但却发现空闲帧列表上没有空闲帧
怎么办
?
- 终止进程
- 交换出一个进程,
页面置换
(page replacement)
1.基本页面置换
如果没有空闲帧,就查找当前不在使用的一个帧,并释放它。
修改缺失错误处理程序,以包括页面置换:
- 找到所需页面的磁盘位置
- 找到一个空闲帧
如果有,就是用它
如果没有,使用页面置换算法来选择一个牺牲帧
将牺牲帧写回
磁盘,修改页表和帧表 - 将所需页
读入
空闲帧,修改页表和帧表 - 从发生缺页错误位置,继续用户进程
页面置换发生两次页传输(换入、换出),导致页处理时间加倍,增加了内存访问时间
如何解决
:
- 方法一(换出):
每个页关联一个修改位
通过修改位确认关联页是否被修改。如果被修改,换出时必需写会磁盘;如果没有,则不需要写回磁盘,从而避免了写入磁盘操作 - 方法二(换入):
系统保留一个空闲帧缓冲池
,当需要牺牲帧写回磁盘时,在这之前,从空闲帧缓冲池得到内存空间,先将所需要的页读入内存,这样就可以提前执行用户进程(先分配后换出
)
页置换算法要考虑的问题
:
如何最小化页错误的发生
,同一个页有可能多次被释放、被载入
利用引用串来评估一个算法:
- 引用串:一系列页的序号
- 评估:检查发生的页错误次数
随着帧数量的增加,缺页错误的数量会降低至最小值
2.FIFO页面置换
即先进先出。
假设有3个帧,以及引用串为:
7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
有下图
为说明使用FIFO算法可能出现的问题,假设如下引用串:
1,2,3,4,1,2,5,1,2,3,4,5
则有:
上图中,假设有3个帧,则可算出缺页错误数为9(最开始向空帧写入也算一次);但假设有4个帧,则缺页错误数为10 !!
这显然违背
了上文提到的帧数和缺页错误数的关系
这种意想不到的结果称为Belady异常
3.最优页面置换
置换将来最长时间不会使用的页面
但问题是我们并不能知道引用串未来的信息,所以最优算法主要用于比较研究
4.LRU页面置换
最近最少使用算法:每个页关联该页上次使用的时间,选择最长时间没有使用的帧
它的主要问题是:如何实现LRU置换
- 计数器
页表的每一项与计数器相连,并计入时间, 但可能会出现如下问题:加了访问操作(需记录时间)、增加内存使用、每次置换需要搜索全部页表 - 栈
每当引用一个页,该页就移动到栈的最顶部,其他依次往下移动,最近不常用的栈放在栈的最低端。采用栈实现方法需要每次更新栈,需要栈中项的移动
5.近似LRU页面置换
1.附加引用位算法
每个页都与引用位相关联
- 每当引用页时,相应页的引用位就被硬件置位
- 开始,引用位被初始化为0
- 页被引用,引用位被设置为1; 没被引用,就设置为0(在规定的时间周期)
- 如一个8位的字节的引用位表示对8个周期进行记录
- 每次引用的记录,放到8位字节的最高位,而将其他位向右移一位,并抛弃最低位
- 每个页都有自己的引用位的值,哪个引用位的值最小,就替换哪个
2.第二次机会算法
基本算法是一种FIFO置换算法,然鹅,每当需要置换页时,检查每页相关联的引用位
- 如引用位为0就置换,并把引用位设置成1
- 如引用位为1就跳过(给第二次机会),并清零,然后跳到下一个FIFO页
3.增强型第二次机会算法
利用2个位,即引用位和修改位
- 第一位表示是否被引用过
- 第二位表示是否被修改过
采用这两个位,有以下可能类型:
- (0, 0)最近没有使用且也没有修改,用于置换的最佳页
- (0, 1)最近没有使用但修改过,不太好的置换,需要写出到磁盘
- (1, 0)最近使用过但没有修改,有可能很快又要被使用
- (1, 1)最近使用过且修改过,有可能很快又要被使用,且置换时需要写出到磁盘。
6.基于计数的页面置换
保留一个用于记录其引用次数的计数器。
有些以下两种方案:
- 最不经常使用页置换算法
理由:经常活动的页应该有更大的引用次数 - 最常使用页置换算法
理由:引用次数少的页可能是刚刚调进来的,将来可能经常用
五、帧分配
每个进程需要分配最小帧数
1.分配算法
- 平均分配方式:每个进程分配物理帧的大小相同
- 比例分配方式:根据进程大小比例
- 优先级分配方式:根据进程优先级分配
2.全局分配和局部分配
页面置换算法分为两大类:全局置换
和局部置换
- 全局置换:从所有帧集中选择一个置换帧
由于可能会置换其他进程的帧,所以进程分配到的帧数量可能发生变化 - 局部置换:仅从自己的分配帧中选择一个置换帧
分配到的帧数量不会发生变化
六、系统抖动(系统颠簸)
因一个进程没有分配到“足够”的页帧,而频繁的发生页错误,这会导致:
- CPU 使用率下降,操作系统会试图增加多道程序的程度
- 进程会试图去抢别的进程的帧
随着多道程序的增加,CPU利用率也增加,直到达到最大值(内存被充分利用),如果多道程序还要增加,那么系统抖动开始,CPU利用率急剧下降
七、内存映射文件
采用虚拟内存技术,以将文件I/O作为常规内存访问,这种方法称为内存映射
,允许一部分虚拟内存与文件进行逻辑管理
1.基本机制
将磁盘块儿(block)映射到内存的一页或多页
- 最开始,访问文件会发生页错误
- 文件的读写就按通常的内存访问来处理
- 文件的写(磁盘)I/O 操作不一定立即发生,而是定期的发生或关闭文件时发生
优点: 文件共享
多个进程可以将同一个文件映射到各自的虚拟内存中,以允许数据共享
八、分配内核内存
用于分配内核内存的空闲内存池
通常不同于普通用户模式进程的列表
- 内核需要为不同大小数据结构请求内存,并努力最小化碎片浪费
- 需要连续分配
下面讨论两个策略,以便管理用于内核进程的空闲内存。
1.伙伴系统
buddy系统
:从物理上连续的、大小固定的段上进行分配
内存按2的幂的大小来进行分配(2,4,8,16…),直到分配合适的页为止。
优点:利用合并技术可将相邻伙伴快速组成更大分段
缺点:造成分配段内的碎片
2.slab系统
每个slab是由一个或多个物理上连续的页组成。
每个cache含有一个或多个slab
- 每个内核数据结构(信号量,文件对象,进程描述符等)都有它的cache,每个cache 含有内核数据结构的对象实例
- 当创建cache 时, 起初包含若干标记为空闲的对象,对象的数量与slab 的大小关联
- 当需要内核数据结构的对象时,可从cache中获取,并标记为使用