程序的局部性:
程序在执行过程中的一个较短时期,所执行的指令地址和指令的操作数地址,分别局限于一定区域
时间局部性
一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都集中在一个较短时期内
空间局部性
当前指令和邻近的几条指令,当前访问的数据和邻近的几个数据都集中在一个较小区域内
分支局部性
一条跳转指令的两次执行,很可能跳到相同的内存位置
例子:
页面大小为4K,分配给每个进程的物理页面数为1。在一个进程中,定义了如下的二维数组int A[1024][1024],该数组按行存放在内存,每一行放在一个页面中(1024 * 4 = 4K)
程序编写方法1: for (j = 0; j < 1024; j++) for (i = 0; i < 1024; i++) A[i][j] = 0;
程序编写方法2: for (i=0; i<1024; i++) for (j=0; j<1024; j++) A[i][j] = 0;
两种数组初始化程序,第一种是按照对每列初始化,第二种是对每行进行初始化
第一种先访问a0,0,缺页将第0页调入内存,然后访问a1,0缺页,由于内存只分配给该进程只有1个页框,所以将第0页调出,第1页调入,一次类推,可得缺页总次数1024*1024
第二种先访问a0,0,缺页将第0页调入内存,然后访问a0,1,a0,1在第0页中,此时还在内存中没有缺页,只有访问下一行是才发生缺页,所以以此类推缺页总次数1024次
虚拟存储技术:
当进程运行时,先将其一部分装入内存,另一部分留在磁盘,当要执行的指令或访问数据不在内存时,由OS自动完成将它们从磁盘调入内存进行工作
虚拟地址空间即为分配给进程的虚拟内存
虚拟地址是在虚拟内存中指令或数据的位置,该位置可以被访问,就好像它是内存的一部分
虚存:
把内存与磁盘有机结合使用得到一个很大内存。
虚存是对内存的抽象,构建在存储体系之上,由操作系统协调各存储器的使用
虚存提供了一个比物理内存空间大得多的地址空间
总结:
虚存的应用背景是用小内存运行大程序。这里的"大程序"是指比整个内存用户空间还要大的程序,它可以是一道程序,也可以是多道程序之和。
虚存的可行基础是程序运行的局部性原理。
实现虚存的主要技术是部分装入、部分对换、局部覆盖、动态重定位。
存储保护:
确保每个进程有独立的地址空间
确保进程访问合法的地址范围
确保进程的操作是合法的
cpu将逻辑地址与(进程空间的基地址+界限寄存器存的进程空间长度)进行比较,一旦不满足条件就会导致异常CPU控制权交给OS处理
虚拟页式:
虚拟存储技术 + 页式存储管理方案
→ 虚拟页式存储管理系统
基本思想:
1.进程开始运行之前,不是装入全部页面,而是装入一个或零个页面
2.之后,根据进程运行的需要,动态装入其他页面(请求调页,预先调页方式)
3.当内存空间已满,而又需要装入新的页面时,则根据某种算法置换内存中的某个页面,以便装入新的页面
主要就是用CPU时间和磁盘空间换取内存空间
页表的设计(一般由硬件完成):
页表项(页号是下标即可):
1.页框号
2.有效位:表示该页是在内存还是在磁盘上 0:磁盘,1:内存
3.访问位:引用位,表示是否访问过
4.修改位:表示该页在内存中是否被修改过
5.保护位:读/可读写
思考:
32位虚拟地址空间的页表规模?页面大小为4K;页表项大小为4字节
则:一个进程地址空间有 ?页(需要知道32位,即进程被创建时就从虚存中分配给它2 ^ 32 = 4GB地址空间,4GB/4K = 2^20页)
其页表需要占 ?页(2^20页就需要有2^20个页表项,2^20 * 4 / 4K = 1024页)
64位虚拟地址空间页面大小为4K;页表项大小为8字节,同理页表规模: 32,000 TB
可以看到64位的页表规模太大,但是64位寻址空间为2^ 64 = 16TB,若32000TB作为单级页表,CPU根本就无法完全访问,而且也没有这么大的内存
此时就要引入多级页表:
此时使用多级页表的好处:它可以被动态的加载,页表有需要的时候,从硬盘上加载进内存,从这一点上来看确实是比单级页表一次性全部加载节省了内存,但是多级页表总量上并没有比单级页表减少,因为多级页表还是要那么多页表项并且还要算上之前的链接比单级的大多了。
页表页在内存中若不连续存放,则需要引入页表页的地址索引表 → 页目录(Page Directory)
二级页表可以表示4GB的空间(2^10页表,每个页表保存2^10项,总共可以记录2^20页,2^20*4K (页面大小)= 4GB)
vpn:virtual page number
PS.i386页目录项与页表项
反置页表:
附上知乎上R大链接:http://www.cs.virginia.edu/~cs333/notes/virtual_memory4.pdf
产生反置页表原因:
例子:
64位虚拟空间,4KB的页面大小,512MB物理内存,那么对于单级页表需要多少空间来存储?
进程地址空间2^64B,页面大小2^12B 推出2^52页,表明单级页表需要有有2^52项
页表项大小:512MB = 2^29B 推出页框总数为2^17页,那么对于简单的页表项中,17位用来表示页框号加上用1字节表示的入口控制位总共需要4字节
所以页表大小为2^52 * 4 = 2^54B
一个页面4K,一个页表项为4B所以一个页表最多1024项 = 2 ^10,所以(2^10)^ 6 > 2 ^ 52,即至少需要6级页表,但是多级页表总的大小还是大于单级页表所以不考虑
此时我们是靠的页表都是从虚拟页号对应到物理页号,为什么我们不能用物理页号无对应虚拟页号呢?因为物理页号比较少
所以引出反置页表:页表项为进程PID和对应的虚拟页号,整个页表为大数组,下标为物理页号(注意还是从虚拟页号转化成物理页号)
假设:pid占16位,虚拟页号占52位(因为之前算出的虚拟页数2^52页),入口控制位为12位,总共页表项为8byte * 物理页数2^17 = 1.3MB远小于单级页表
根据反置页表,从头到尾找对应的pid和虚拟页号,从而下标即为物理页号,但是这里出现了问题,从头到尾找效率不高,所以又加上hash算法
首先pid和vpn(虚拟页号)通过hash函数找到页表对应的下标,发现vpn和pid不符,可能之前hash出来的值发生碰撞,经过碰撞处理函数后移了,所以通过next找到下一个相同hash值,发现vpn和pid对上了所以此时下标即为物理页号
快表(TLB)引入:
页表 → 两次或两次以上的对相同的内存地址访问
CPU的指令处理速度与内存指令的访问速度差异大,CPU的速度得不到充分利用
这问题跟CPU与内存之间为了提升效率,在CPU和内存之间加上cache原理类似。
快表:
一种随机存取型存储器,除连线寻址机制外,还有接线逻辑,能按特定的匹配标志在一个存储周期内对所有的字同时进行比较。
性质:相联存储器(associative memory)
特点:
按内容并行查找,保存正在运行进程的页表的子集(部分页表项)(就不经过查整个页表来得到页框号,直接利用较小的快表得到页框号快速访问内存)
引入快表后的地址转换:
首先mmu通过查找较小的快表,如果命中,那么直接页框号和页内偏移即可得到物理地址,如果不命中,那么只能通过查找页表,若对应的页表项中的有效位为0,即该页不在内存中,则发生page fault(缺页),CPU控制权交给OS,让OS从硬盘上调入相应的页到内存中。
页错误(page fault)
又称 页面错误、页故障、页面失效
原因
地址转换过程中硬件产生的异常
具体原因
1.所访问的虚拟页面没有调入物理内存;例如缺页异常
2.页面访问违反权限(读/写、用户/内核)
3.错误的访问地址,例如访问进程地址空间的空白区域
缺页异常:
是一种Page Fault,在地址映射过程中,硬件检查页表时发现所要访问的页面不在内存,则产生该异常——缺页异常
解决方案:
操作系统执行缺页异常处理程序:获得磁盘地址,启动磁盘,将该页调入内存
如果内存中有空闲页框,则分配一个页框,将新调入页装入,并修改页表中相应页表项的有效位及相应的页框号
若内存中没有空闲页框,则要置换内存中某一页框;若被置换的页框内容被修改过,则要将其写回磁盘
驻留集:
驻留集大小:给每个进程分配多少页框?
固定分配策略
进程创建时确定,可以根据进程类型(交互、批处理、应用类)或者基于程序员或系统管理员的需要来确定,一旦确定无法改变
可变分配策略
根据缺页率评估进程局部性表现
1.缺页率高→增加页框数
2.缺页率低→减少页框数
3.系统开销
PS.分配页框号和缺页率的关系
置换问题:
置换范围:
1.局部置换策略
仅在产生本次缺页的进程的驻留集中选择
2.全局置换策略
将内存中所有未锁定的页框都作为置换的候选
对于一个可变分配且实用局部置换策略的OS:
1.当一个新进程装入内存时,给它分配一定数目的页框,然后填满这些页框
2.当发生一次缺页异常时,从产生缺页异常进程的驻留集中选择一页用于置换
3.不断重新评估进程的页框分配情况,增加或减少分配给它的页框,以提高整体性能
最佳置换策略:
置换最近最不可能访问的页,这个思想主要是基于过去的行为来预测将来的行为,但是一旦置换策略越精细,软硬件开销就越大。
有个约束条件:
不能置换被锁定的页框
页框锁定:
给每一页框增加一个锁定位,通过设置相应的锁定位,不让操作系统将进程使用的页面换出内存,避免产生由交换过程带来的不确定的延迟
这些页框可能为:操作系统核心代码、关键数据结构、I/O缓冲区
清除策略:
清除:从进程的驻留集中收回页框
虚拟页式系统工作的最佳状态:发生缺页异常时,系统中有大量的空闲页框
结论:在系统中保存一定数目的空闲页框供给比使用所有内存并在需要时搜索一个页框有更好的性能
分页守护进程(paging daemon):
多数时间睡眠着,可定期唤醒以检查内存的状态
如果空闲页框过少,分页守护进程通过预定的页面置换算法选择页面换出内存
如果页面装入内存后被修改过,则将它们写回磁盘,分页守护进程可保证所有的空闲页框是“干净”的,没有被修改过的(一旦修改过的就要在往这个页框调入逻辑页之前,把这个页框内容写回磁盘)
当进程需要使用一个已置换出的页框时,如果该页框还没有被新的内容覆盖,将它从空闲页框集合中移出即可恢复该页面(即被置换后的页框如果还没有被新内容覆盖,会属于空闲页框集合中,还是在内存中,再次使用调出即可)
页缓冲技术:
不丢弃置换出的页,将它们放入两个表之一:如果未被修改,则放到空闲页链表中,如果修改了,则放到修改页链表中
被修改的页定期写回磁盘(不是一次只写一个),这大大减少I/O操作的数量,从而减少了磁盘访问时间
被置换的页仍然保留在内存中,一旦进程又要访问该页,可以迅速将它加入该进程的驻留集合(代价很小)
置换算法:
最佳页面置换算法:
置换以后不再需要的或最远的将来才会用到的页面
但是很难实现,只能作为其他算法的参考
先进先出算法:
选择在内存中驻留时间最长的页并置换它
实现:页面链表法(某个进程进入内存挂到该链表末尾,每次置换掉链表头对应的页框)
第二次机会算法:
对FIFO算法的改进,按照先进先出算法选择某一页面,检查其访问位R,如果为0,则置换该页;如果为1,则给第二次机会,并将访问位 置0,将其挂到链尾,从头接着再往下寻找
时钟算法:
对第二次算法的改进,第二次算法中摘下头放到链尾有开销,所以针对其进行改进
左图页框指针指向2,发现2这个页被访问过,将访问位置0,指针下移,可以看到被置0的那一页就相当于掉到了链尾,3页同理,发现4页没被访问过,将4页置换然后将访问位置0,之后指针下移,一次置换结束
最近未使用算法(NRU):
思想:选择在最近一段时间内未使用过的一页并置换
实现:设置页表表项的两位访问位(R), 修改位(M)(这些由硬件完成若不行就软件完成),启动一个进程时,R、M位置0,R位被定期清零(复位,因为如果有段时间没被访问就置零)
发生缺页中断时,操作系统检查R,M:
第1类:无访问,无修改
第2类:无访问,有修改
第3类:有访问,无修改
第4类:有访问,有修改
算法思想:
随机从编号最小的非空类中选择一页置换
对NRU算法的时钟算法实现:
1.从指针的当前位置开始,扫描页框缓冲区,选择遇到的第一个页框(r=0;m=0)用于置换(本扫描过程中,对使用位不做任何修改)
2.如果第一步找不到r=0,m = 0的页框,则重新回到初始地方扫描,选择第一个(r=0;m=1)的页框(本次扫描过程中,对每个跳过的页框,将其使用位设置成0)
3.如果第2步没找到r = 0,m = 1的页框,指针将回到它的最初位置,并且集合中所有页框的使用位均为0(被第二步复位)。重复第1步,并且,如果有必要,重复第2步
最近最少使用算法(LRU):
设计思想:选择最后一次访问时间距离当前时间最长的一页并置换,即置换未使用时间最长的一页
性能:接近OPT
实现:对每个页框打上时间戳 或 维护一个访问页的栈,但是对于那么多的页框系统开销大
LRU硬件实现:
对于内存只有0,1,2,3页面选择置换
访问顺序:0, 1, 2, 3, 2, 1, 0, 3, 2, 3
将0行置1,0列置0,有先后顺序,之后类似,最终将每行结果加起来,置换和最小的对应的页。
这种方法开销很大,因为如果页面变多,这个矩阵也变大,硬件实施也困难
最不经常使用算法:
选择访问次数最少的页面置换,LRU的一种软件解决方案
实现:
1.软件计数器,一页一个,初值为0
2.每次时钟中断时,计数器加R
3.发生缺页中断时,选择计数器值最小的一页置换
老化算法(对软件LRU的改进):
设计思想:
计数器在加R前先将计数器值右移一位,R位加到计数器的最左端
第一次0,1,2,3,4,5对应的访问位为1,0,1,0,1,1所以计数器值分别为0页右移左加1,1页右移一位,。。。。,最终计数器值小的对应的页面被置换出去。
例子:
系统给某进程分配3个页框(固定分配策略),初始为空
进程执行时,页面访问顺序为:2 3 2 1 5 2 4 5 3 2 5 2
FIFO算法:
首先2进入缺页,3进入缺页,2进入不缺页,1进入缺页,5进入缺页而且要置换,换出最早进入的2,之后2进入缺页而且要置换,换出最早进入的3,4进入缺页并置换,换出最早进入的1,5进入不缺页,3进入缺页并置换,换出最早进入的5,2进入不缺页,5进入缺页并置换,换出最早进入的2,2进入缺页并置换,换出最早的4
缺页次数:9
LRU算法:
首先2进入缺页,3进入缺页,2进入不缺页,1进入缺页,5进入缺页而且要置换,换出最近没使用的3,之后2进入不缺页,4进入缺页并置换,换出最近没使用的1,5进入不缺页,3进入缺页并置换,换出最近没使用的2,2进入缺页并置换,换出最近没使用的4,5进入不缺页,2进入不缺页(技巧:就看页框数多少,然后当前页往回退多少就置换该页面,例如:第一次5缺页,换出往前推3个的第3页)
缺页次数:7
OPT算法:
首先2进入缺页,3进入缺页,2进入不缺页,1进入缺页,5进入缺页而且要置换,换出之后没使用的1,之后2进入不缺页,4进入缺页并置换,换出之后没使用或者使用跨度最大的的2,5进入不缺页,3进入不缺页,2进入缺页并置换,换出之后没使用的4,5进入不缺页,2进入不缺页
缺页次数:6
由此看来LRU算法是最接近最优算法
BELADY现象:
1 2 3 4 1 2 5 1 2 3 4 5访问页面顺序
采用FIFO算法,计算当 m=3 和 m=4 时的缺页中断次数
m=3时,缺页中断9次;m=4时,缺页中断10次。
BELADY:FIFO页面置换算法会产生异常现象,即:当分配给进程的物理页面数增加时,缺页次数反而增加
工作集算法:
影响缺页次数因素:
页面置换算法
页面本身的大小 (页面设计如果太小,指令就少,需要频繁从磁盘加载页面到内存增加了缺页次数,太大内存中并发程序数目减小,效率下降)
程序的编制方法 (参见程序局限性原理)
分配给进程的页框数量
颠簸(Thrashing,抖动)
虚存中,页面在内存与磁盘之间频繁调度,使得调度页面所需的时间比进程实际运行的时间还多,这样导致系统效率急剧下降,这种现象称为颠簸或抖动
工作集算法:
设计思想:
根据程序的局部性原理,一般情况下,进程在一段时间内总是集中访问一些页面,这些页面称为活跃页面,如果分配给一个进程的物理页面数太少了,使该进程所需的活跃页面不能全部装入内存,则进程在运行过程中将频繁发生缺页中断,如果能为进程提供与活跃页面数相等的物理页面数,则可减少缺页中断次数
工作集模型:
一个进程在执行的页框集合
工作集W(t,Δ) = 该进程在某时刻t的过去的Δ个虚拟时间单位中访问到的页面的集合
内容取决于三个因素:
1.访页序列特性,访问序列不同得到的工作集也不同
2.时刻t
3.工作集窗口长度(Δ),窗口越大,工作集就越大
例子:
26157775162341234443434441327
| ------10-----|t1 |------10-----|t2
W(t1,10)={1,2,5,6,7}
W(t2,10)={3,4}
基本思路:
找出一个不在工作集中的页面并置换它(置换不是在某时刻t的过去的Δ个虚拟时间单位中的页面)
思想:
1.每个页表项中有一个字段:记录该页面最后一次被访问的时间
2.设置一个时间值T
3.判断:
根据一个页面的访问时间是否落在“当前时间-T”之前或之中决定其在工作集之外还是之内
实现:
扫描所有页表项,执行操作
1. 如果一个页面的R位是1,则将该页面的最后一次访问时间设为当前时间,将R位清零
2. 如果一个页面的R位是0,则检查该页面的访问时间是否在“当前时间-T”之前
(1) 如果是,则该页面为被置换的页面;
(2) 如果不是,记录当前所有被扫描过页面的最后访问时间里面的最小值(离当前最远的页面)。扫描下一个页面并重复1、2
内存映射文件:
基本思想:
进程通过一个系统调用(mmap)将一个文件(或部分)映射到其虚拟地址空间的一部分,访问这个文件就像访问内存中的一个大数组,而不是对文件进行读写
特点:
在多数实现中,在映射共享的页面时不会实际读入页面的内容,而是在访问页面时,页面才会被每次一页的读入,磁盘文件则被当作后备存储
当进程退出或显式地解除文件映射时,所有被修改页面会写回文件
即操作都在进程虚拟地址空间上,并且只有该页面要被访问时才将磁盘文件内容加载进来,而且只有等进程结束时才会把修改后的文件写回给磁盘
写时复制技术:
可以看到两个进程由于一样而共享物理页,这就相当于Linux调用fork函数是父子进程的关系,当子进程要往页面写入东西时,被OS捕捉,OS会在此时创建出与父进程一样的页面例如页面2,新复制的页面对执行写操作的进程是私有的,对其他共享写时复制页面的进程是不可见的(复制的页面2对子进程私有,对父进程不可见)
作者水平有限,文章肯定有错还请各位指点!!!感谢!!!
参考链接:https://support.microsoft.com/zh-cn/help/2160852/ram--virtual-memory--pagefile--and-memory-management-in-windows
https://www.nowcoder.com/questionTerminal/d7f5fe2063e64573819f36484caf0beb