背景
- 事实上,我们发现,在许多情况下,并不需要将整个程序放入到内存中
- 如:处理异常错误条件的代码,这些错误即使有,也很少发生
- 如:虽然汇编程序系统表可能有3000个符号空间,但是可能用到的只有不到200
- 如:程序的某些选项或功能可能很少使用
- 能够执行,只有部分在内存中的程序的好处:
- 程序不再受现有物理内存空间限制,可以为一个巨大的虚拟地址空间编写程序,简化了编程工作量
- 因为每个用户程序使用了更少的物理内存,所以更多的程序可以同时执行,CPU使用率也相应增加
- ??由于载入或交换每个用户程序到内存所需的I/O会更少,用户程序运行的更快
- 虚拟内存也允许文件和内存通过共享页而为两个或多个进程所共享
- 1:
- 通过将共享对象映射到虚拟地址空间,系统库可为多个进程所共享
- 虽然每个进程都认为共享库是其虚拟地址空间的一部分
- 而共享库所用的物理内存的实际页,是为所有进程所共享
- 2:
- 虚拟内存允许进程共享内存
按需调页
- 按需调页:
- 程序中可能存在选项执行的代码
- 不一次性将所有选项都加载到内存
- 而是在运行时,需要时才调入相应的页
- 由于将进程看做是一系列的页,因此按需调页时,使用的是调入一部分页,而不是整个程序。这个控制调入的程序称为调页程序
- 有效-无效位:
- 有效时表示相关页合法且在内存中
- 无效时表示,页无效,或者有效但不在磁盘上
- 如果进程试图访问尚未调入内存的页:
- 1.检车进程内部页表,以确定该引用是否合法
- 2.如果非法就终止进程。如果有效,此时就调入该页
- 3.找到一个空闲帧,调度,将所需要的页调入该帧
- 4.修改进程的内部表和页表,表示该页已在内存
- 5.再次执行该页
写时复制
-
这种方法允许父进程和子进程开始时共享同一页面
-
如果任何一个进程需要对页进行写操作,那么就创建一个共享页的副本
-
从哪里分配空闲页是很重要的,许多操作系统为这类请求提供了空闲缓冲池
页面置换
- 当出现页错误的时候,操作系统会确定所需页在磁盘上的位置
- 但是却发现空闲帧列表上没有空闲帧,所有内存都在使用
- 过程
- 查找所需页在磁盘上的位置
- 查找一个空闲帧:如果有空闲帧,就是用;如果没有,那么就使用页置换算法选择一个牺牲帧;将牺牲帧的内容写到磁盘上,改变页表和帧表
- 将所需页读入新空闲帧,改变页表和帧表
- 重启用户进程
- 引用串就是我们使用的页的串,当内存中没有时,就会出现错误,需要页面置换
FIFO页置换
- 下图可以看出,常用的0,被反复置换出去,其实效率也不高
- 共产生了15次帧错误
最优置换
- 置换未来最长时间不会使用的页
- 就是在串内,未来最长时间都不会用到谁,我就换谁
- 共产生9次错误
- 2换7:因为7在倒数第三个才再次使用
- 3换1:因为2在之后三次就会使用,0在下次就会使用,而1,在8次后才使用
- 等等等
LUR(least recently used最近最少使用)页置换
- 将目前最不常使用的替换掉
- 注意这和FIFO的区别:
- FIFO在第一个3的地方,对于012来说,0是最先进来的,因此此次就会将0替换出来
- LUR在第一个3的地方,因为0上次才使用过,2上上次才使用过,1是上上上次使用的,1是最不常用的,因此将1替换出来
- LUR算法产生了12次错误
- 实现
- 1.可以使用时间计数
- 为每一个页表关联一个使用时间域
- 每次使用后,就更新该时间
- 这时,时间最早的,就是将要被提花内的
- 2.使用栈
- 每次新引用的页,就放到栈顶
- 栈底的就是要替换的
近似LRU页置换
- 很少有计算机能提供足够的硬件来支持真正的LRU页置换
- 近似就是指的,给每个页一个引用位,引用后就置1
- 虽然不能知道确切的顺序信息,但是能够知道是否被使用过
1.附加引用位算法
- 可以为内存中的每个表中的页保留一个8位1字节
- 设置一个时间间隔,这8位就代表着,该页在过去8个时间间隔内的使用情况
- 每过去一个时间间隔,就将这8位右移一位,并在最左侧一位添加引用位
- 最小的就是LRU页
- 当然,附加位使用8位还是几位,可以自己决定
2.二次机会算法
- 即,0为附加位,即只用引用位的算法
- 每当使用过该页,就将其引用位置1
- 其基本算法是FIFO算法
- 需要置换时,先找到FIFO的页
- 其引用位是0,就置换,是1,就将其引用位置0,即再给他一次机会
- 然后找下一个FIFO的页,检查其引用位
3.增强型二次机会算法
- 在使用引用位的基础上,增加了修改位
- (0,0)最近没有使用也没有修改——置换的最佳页
- (0,1)最近没有使用但是有修改——不是很好,因为置换之前需要将页写出到磁盘
- (1,0)最近有使用,无修改——不是很好,因为可能会被再次使用
- (1,1)最近有使用,也有修改——最不好,不仅可能会被再次使用,而且置换之前需要将页写出到磁盘
基于计数的页置换
不常用,且费时
页缓冲算法
- 就是永远保留一个空闲帧池
- 当需要替换时,直接将所需替换的页读入到内存即可
- 等牺牲帧写出后,再将牺牲帧作为空闲帧池
- 等于是省去了中间等待写出的时间
帧分配
- 如何在各个进程之间分配一定的空闲内存
帧的最少数量
- 当指令完成之前出现页错误,该指令必须重新执行
- 因此,必须有足够的帧来容纳所有单个指令所引用的页
分配算法
- 平均分配
- 比例分配:根据进程的大小来分配。如一个进程为10页,一个进程为127页。共有62帧内存。则:10/137=4,127/137=57
全局分配与局部分配
- 全局置换:
- 允许一个进程从所有帧的集合中选择一个置换帧,而不管该帧是否已分配给其他进程。
- 即一个进程可以从另一个进程中拿到帧
- 局部置换:
- 进程只能从自己已有的帧中置换
系统颠簸
- 频繁的页调度行为称为颠簸
- 如果一个进程在换页上用的时间要多余执行时间,那么这个进程就在颠簸
- 这会使CPU的利用率急剧下降
- 为了增加利用率,降低颠簸,必须降低多道程序的程度
- 也会导致CPU利用极具下降
产生的原因
- 1.可能是因为一个进程分配的帧太少
- 2.进程产生页错误,需要置换某个页
- 3.但是现有的页都在使用中
- 4.于是替换任意一个页,就会接着产生页错误,不断循环
页错误率
- 如果某进程页错误率很低,那么说明其帧分配过多
- 如果也错误率很高,说明其帧分配太少
- 我们要做的就是设置一个上下限
- 如果低于错误率,就给进程移除帧
- 高于错误率,就给进程分配帧
- 如果没有可用帧,就要暂停进程,释放帧,分配给那些高页错误率的进程
逻辑地址 Vs 物理地址 Vs 虚拟内存
-
所谓的逻辑地址
-
是指计算机用户(例如程序开发者),看到的地址。
-
例如,当创建一个长度为100的整型数组时,操作系统返回一个逻辑上的连续空间:指针指向数组第一个元素的内存地址。由于整型元素的大小为4个字节,故第二个元素的地址时起始地址加4,以此类推。
-
事实上,逻辑地址并不一定是元素存储的真实地址,即数组元素的物理地址(在内存条中所处的位置),并非是连续的,只是操作系统通过地址映射,将逻辑地址映射成连续的,这样更符合人们的直观思维。
-
虚拟内存
-
操作系统读写内存的速度可以比读写磁盘的速度快几个量级。但是,内存价格也相对较高,不能大规模扩展。于是,操作系统可以通过将部分不太常用的数据移出内存,“存放到价格相对较低的磁盘缓存,以实现内存扩展。
-
操作系统还可以通过算法预测哪部分存储到磁盘缓存的数据需要进行读写,提前把这部分数据读回内存。虚拟内存空间相对磁盘而言要小很多,因此,即使搜索虚拟内存空间也比直接搜索磁盘要快。
-
唯一慢于磁盘的可能是,内存、虚拟内存中都没有所需要的数据,最终还需要从硬盘中直接读取。
-
这就是为什么内存和虚拟内存中需要存储会被重复读写的数据,否则就失去了缓存的意义。现代计算机中有一个专门的转译缓冲区(Translation Lookaside Buffer,TLB),用来实现虚拟地址到物理地址的快速转换。
虚拟内存意义:
- 虚拟内存使整个地址空间可以用相对较小的单元映射到物理内存。比如物理内存只有4GB,但可以使用远大于它的虚拟内存逻辑地址来映射到4GB的物理地址。
- 将地址空间分割成固定大小的页面,在运行程序时不必将全部程序加载到内存,只需装入当前执行的代码和用到的数据。
- 当发生缺页中断,当前进程忙着将页面从磁盘读入到内存时,可以切换进程,将CPU交给其他进程使用