一、缺页异常(缺页中断)
当 CPU 访问的⻚⾯不在物理内存时,便会产⽣⼀个缺⻚中断,请求操作系统将所缺⻚调⼊到物理内存。那它与⼀般中断的主要区别在于:
- 缺⻚中断在指令执⾏「期间」产⽣和处理中断信号,⽽⼀般中断在⼀条指令执⾏「完成」后检查和处理中断信号。
- 缺⻚中断返回到该指令的开始重新执⾏「该指令」,⽽⼀般中断返回回到该指令的「下⼀个指令」执 ⾏。
缺⻚中断的处理流程,如下图:
- 在 CPU ⾥访问⼀条 Load M 指令,然后 CPU 会去找 M 所对应的⻚表项。
- 如果该⻚表项的状态位是「有效的」,那 CPU 就可以直接去访问物理内存了,如果状态位是「⽆效的」,则 CPU 则会发送缺⻚中断请求。
- 操作系统收到了缺⻚中断,则会执⾏缺⻚中断处理函数,先会查找该⻚⾯在磁盘中的⻚⾯的位置。
- 找到磁盘中对应的⻚⾯后,需要把该⻚⾯换⼊到物理内存中,但是在换⼊前,需要在物理内存中找空闲⻚,如果找到空闲⻚,就把⻚⾯换⼊到物理内存中。
- ⻚⾯从磁盘换⼊到物理内存完成后,则把⻚表项中的状态位修改为「有效的」。
- 最后,CPU 重新执⾏导致缺⻚异常的指令。
第 4 步是能在物理内存找到空闲⻚的情况,那如果找不到呢?
找不到空闲⻚的话,就说明此时内存已满了,这时候,就需要「⻚⾯置换算法」选择⼀个物理⻚,如果该物理⻚有被修改过(脏⻚),则把它换出到磁盘,然后把该被置换出去的⻚表项的状态改成「⽆效的」,最后把正在访问的⻚⾯装⼊到这个物理⻚中。
⻚表项通常有如下图的字段:
- 状态位:⽤于表示该⻚是否有效,也就是说是否在物理内存中,供程序访问时参考。
- 访问字段:⽤于记录该⻚在⼀段时间被访问的次数,供⻚⾯置换算法选择出⻚⾯时参考。
- 修改位:表示该⻚在调⼊内存后是否有被修改过,由于内存中的每⼀⻚都在磁盘上保留⼀份副本,因此,如果没有修改,在置换该⻚时就不需要将该⻚写回到磁盘上,以减少系统的开销;如果已经被修改,则将该⻚重写到磁盘上,以保证磁盘中所保留的始终是最新的副本。
- 硬盘地址:⽤于指出该⻚在硬盘上的地址,通常是物理块号,供调⼊该⻚时使⽤。
二、虚拟内存的管理流程
所以,⻚⾯置换算法的功能是,当出现缺⻚异常,需调⼊新⻚⾯⽽内存已满时,选择被置换的物理⻚⾯, 也就是说选择⼀个物理⻚⾯换出到磁盘,然后把需要访问的⻚⾯换⼊到物理⻚。
那其算法⽬标则是,尽可能减少⻚⾯的换⼊换出的次数,常⻅的⻚⾯置换算法有如下⼏种:
- 最佳⻚⾯置换算法(OPT)
- 先进先出置换算法(FIFO)
- 最近最久未使⽤的置换算法(LRU)
- 时钟⻚⾯置换算法(Lock)
- 最不常⽤置换算法(LFU)
三、内存页面置换算法
1、最佳页面置换算法
最佳⻚⾯置换算法基本思路是,置换在「未来」最⻓时间不访问的⻚⾯。
所以,该算法实现需要计算内存中每个逻辑⻚⾯的「下⼀次」访问时间,然后⽐较,选择未来最⻓时间不访问的⻚⾯。
举个例⼦,假设⼀开始有 3 个空闲的物理⻚,然后有请求的⻚⾯序列,那它的置换过程如下图:
在这个请求的⻚⾯序列中,缺⻚共发⽣了 7 次(空闲⻚换⼊ 3 次 + 最优⻚⾯置换 4 次),⻚⾯置换共发⽣了 4 次。
这很理想,但是实际系统中⽆法实现,因为程序访问⻚⾯时是动态的,我们是⽆法预知每个⻚⾯在「下⼀次」访问前的等待时间。
所以,最佳⻚⾯置换算法作⽤是为了衡量算法的效率,算法效率越接近该算法的效率,那么说明我们自己写的算法是⾼效的。
2、先进先出置换算法
既然我们⽆法预知⻚⾯在下⼀次访问前所需的等待时间,那我们可以选择在内存驻留时间很⻓的⻚⾯进⾏中置换,这个就是「先进先出置换」算法的思想。
以前⾯的请求的⻚⾯序列作为例⼦,假设使⽤先进先出置换算法,则过程如下图:
在这个请求的⻚⾯序列中,缺⻚共发⽣了10 次,⻚⾯置换共发⽣了 7 次,跟最佳⻚⾯置换算法⽐较起来,性能明显差了很多。
3、最近最久未使⽤的置换算法
最近最久未使⽤(LRU)的置换算法的基本思路是,发⽣缺⻚时,选择最⻓时间没有被访问的⻚⾯进⾏置换,也就是说,该算法假设已经很久没有使⽤的⻚⾯很有可能在未来较⻓的⼀段时间内仍然不会被使⽤。
这种算法近似最优置换算法,最优置换算法是通过「未来」的使⽤情况来推测要淘汰的⻚⾯,⽽ LRU 则是通过「历史」的使⽤情况来推测要淘汰的⻚⾯。
假设使⽤最近最久未使⽤的置换算法,则过程如下图:
在这个请求的⻚⾯序列中,缺⻚共发⽣了 9 次,⻚⾯置换共发⽣了 6 次,跟先进先出置换算法⽐较起来,性能提⾼了⼀些。
-
虽然 LRU 在理论上是可以实现的,但代价很⾼。为了完全实现 LRU,需要在内存中维护⼀个所有⻚⾯的链表,最近最多使⽤的⻚⾯在表头,最近最少使⽤的⻚⾯在表尾。
-
困难的是,在每次访问内存时都必须要更新「整个链表」。在链表中找到⼀个⻚⾯,删除它,然后把它移动到表头是⼀个⾮常费时的操作。
所以,LRU 虽然看上去不错,但是由于开销⽐较⼤,实际应⽤中⽐较少使⽤。
4、时钟页面置换算法
那有没有⼀种即能优化置换的次数,也能⽅便实现的算法呢?
时钟⻚⾯置换算法就可以两者兼得,它跟 LRU 近似,⼜是对 FIFO 的⼀种改进。
该算法的思路是,把所有的⻚⾯都保存在⼀个类似钟⾯的「环形链表」中,⼀个表针指向最⽼的⻚⾯。
当发⽣缺⻚中断时,算法⾸先检查表针指向的⻚⾯:
- 如果它的访问位位是 0 就淘汰该⻚⾯,并把新的⻚⾯插⼊这个位置,然后把表针前移⼀个位置;
- 如果访问位是 1 就清除访问位,并把表针前移⼀个位置,重复这个过程直到找到了⼀个访问位为 0 的⻚⾯为⽌;
时钟⻚⾯置换算法的⼯作流程图如下:
5、最不常⽤算法
最不常⽤(LFU)算法,当发⽣缺⻚中断时,选择「访问次数」最少的那个⻚⾯,并将其淘汰。
它的实现⽅式是,对每个⻚⾯设置⼀个「访问计数器」,每当⼀个⻚⾯被访问时,该⻚⾯的访问计数器就累加 1。在发⽣缺⻚中断时,淘汰计数器值最⼩的那个⻚⾯。
看起来很简单,每个⻚⾯加⼀个计数器就可以实现了,但是在操作系统中实现的时候,我们需要考虑效率和硬件成本的。
-
要增加⼀个计数器来实现,这个硬件成本是⽐较⾼的,另外如果要对这个计数器查找哪个⻚⾯访问次数最⼩,查找链表本身,如果链表⻓度很⼤,是⾮常耗时的,效率不⾼。
-
但还有个问题,LFU 算法只考虑了频率问题,没考虑时间的问题,⽐如有些⻚⾯在过去时间⾥访问的频率很⾼,但是现在已经没有访问了,⽽当前频繁访问的⻚⾯由于没有这些⻚⾯访问的次数⾼,在发⽣缺⻚中断时,就会可能会误伤当前刚开始频繁访问,但访问次数还不⾼的⻚⾯。
那这个问题的解决的办法还是有的,可以定期减少访问的次数,⽐如当发⽣时间中断时,把过去时间访问的⻚⾯的访问次数除以 2,也就说,随着时间的流失,以前的⾼访问次数的⻚⾯会慢慢减少,相当于加⼤ 了被置换的概率。
整理自小林coding所著的《图解系统》,仅做学习用,侵删