按需加载与页替换

缺页与换页

进程加载

  1. 简单方法
    • 将整个进程加载进内存 -> 运行它 -> 退出
  2. 问题
    • 慢(特别是对于大的进程)
    • 浪费空间(一个进程并不是每时每刻都需要所有的内存)
  3. 解决办法
    • 按需加载页:只将实际使用的页加载进内存
    • 换页:内存空间有限,只放频繁使用的那些页
  4. 机制
    • 一部分虚存映射到内存,另一部分虚存映射到磁盘

缺页处理

  1. 内存访问(先查TLB)
  2. 若TLB不命中,则进行页表查找,找到PTE
  3. 若PTE的valid位=0(页不在内存),则触发缺页(Page Fault)
  4. 虚存管理中的缺页处理接管控制,将页从磁盘读到内存
  5. 更新PTE:填入PP#,将Valid位置1
  6. 重新执行该指令:重新进行内存访问

发生缺页后,如何切换到缺页处理?

  • 缺页可能发生在一条指令执行的中途
  • 应用程序透明:必须让用户程序不感知缺页
  • 需要保存状态并从断点处继续执行

页替换

  1. 需要的页不再内存里 -> 需换入 -> 需为它分配一个页框
  2. 可能此时没有空闲页框 -> VM需要进行页替换

包含页替换的缺页处理算法

  1. 进程A发生缺页,发生缺页的页记为VP
    • 陷入内核,保存进程A的当前状态: PC和寄存器
    • 调用OS的缺页处理模块
      • 检查地址和操作类型的合法性,若不合法则终止进程
      • 为VP分配一个物理页框,记为PP
        • 如果有空闲页框PP1,则用它,记PP=PP1
        • 如果没有空闲页框,选择一个状态为used页框PP2
          • 如果他是脏的(M位=1),则把它写回磁盘
          • PTE表项的valid置0,刷新TLB
          • 写回完成后,PP=PP2
      • 找到VP对应的磁盘页,把它读到这个页框(PP)中
      • 修改VP的PTE:填入PP#,将valid位置1,并把该PTE加载进TLB
    • 恢复进程A的状态,重新执行发生缺页的指令
  2. 通用数据结构
    • 空闲页框链表
    • 一张映射表:页框->PID和虚页

页替换算法

  1. 随机选一页
  2. 最优算法(MIN)
  3. NRU(Not Recently Used)
  4. FIFO(First-In-First-Out)
  5. FIFO with Second Chance
  6. NFU(Not Frequently Used)
  7. LRU(Least Recently Used)
  8. Aging(Approximate LRU)
  9. Clock
  10. Working Set
  11. WSClock

最优算法(MIN)

  1. 算法
    • 替换在未来最长一段时间里不用的页
    • 前提:知道未来所有的访问
  2. 例子
    • 内存大小为4个页
    • 访问序列:1,2,3,4,1,2,5,1,2,3,4,5
    • 一共发生6次缺页
  3. 好处
    • 是最优的方案,可以作为一种离线的分析手段
  4. 坏处
    • 在线系统无法采用,因为不知道未来访问的顺序
    • 没有线性时间复杂度的实现

FIFO

  1. 算法
    • 选择最老的页扔掉
  2. 例子
    • 内存大小为4个页
    • 访问序列:1,2,3,4,1,2,5,1,2,3,4,5
    • 一共发生10次缺页
  3. 好处
    • 实现开销最小
  4. 坏处
    • 频繁使用的页被替换

能否通过增加页的数量来减少缺页?

  1. 假设内存大小为3个页,访问序列:1,2,3,4,1,2,5,1,2,3,4,5,只发生9次缺页
  2. "Belady's Anomaly"现象:采用FIFO算法时,有时会出现分配页数增多,缺页次数反而升高的异常现象

FIFO with Second Chance

  1. 核心思想
    • 尽量让频繁使用的页留在内存,不被替换
    • 替换时给访问过的页第二次机会,在内存中呆更长的时间
  2. 算法
    • 检查最老页的R位,如果为0,则将其替换
    • 如果为1,则将R位清0,将该页移到队尾,继续查找
  3. 例子
    • 内存大小为4个页
    • 访问序列:1,2,3,4,1,2,5,1,2,3,4,5
    • 一共发生8次缺页
  4. 好处
    • 实现简单
  5. 坏处
    • 最坏情况可以需要很长时间

Clock

  1. 改进Second Chance的替换开销
    • 移动开销
  2. 算法:把所有页框组织成环形链表
    • 用一个表针指向最老的页
    • 发生缺页的时候,按表针走动方向来检查页
    • 第二次机会
      • 如果R位为1,则将R位清0,且表针向前移一格
      • 如果R位为0,则将该页替换,且表针向前移一格
  3. 与Second Chance算法相比
    • 更加高效
  4. 如果内存很大
    • 轮转一遍需要很长的时间

LRU

  1. 替换最长时间没有使用的页
    • 将所有页框组织成一个链表
    • 前端为最久未访问页(LRU端):替换的页
    • 后端为最近刚访问的页(MRU端):新加载的页和命中的页
    • 每次命中将页重新插入 MRU 端
  2. 例子
    • 内存大小为4个页
    • 访问序列:1,2,3,4,1,2,5,1,2,3,4,5
    • 一共发生8次缺页
  3. 好处
    • 对 MIN 算法的很好近似
  4. 坏处
    • 实现困难

NRU(Not Recently Used)

  1. 算法
    • 按下面顺序,随机选择一个页
      • 未访问过 且 未修改过
      • 未访问过 且 修改过
      • 访问过 且 未修改过
      • 访问过 且 修改过
  2. 例子
    • 内存大小为4个页
    • 访问序列:1,2,3,4,1,2,5,1,2,3,4,5
    • 一共发生7次缺页
  3. 好处
    • 实现简单
  4. 坏处
    • 需要扫描内存中所有页的R位和M位

LFU(Least Frequently Used)

  1. 算法:记录每个页的访问次数,替换访问次数最少的页
    • 每页有一个访问计数器,用软件模拟
    • 每个时钟中断时,所有页的计数器分别和它的R位值相加
  2. 例子
    • 内存大小为4个页
    • 访问序列:1,2,3,4,1,2,5,1,2,3,4,5
    • 一共发生8次缺页
  3. 坏处
    • 过去访问频繁、现在不访问的页,可能无法被替换出去

Aging

  1. 在LFU的基础上消除过去访问的影响
    • 每个时钟中断时,先将所有页计数器右移1位,再将每页计数器最高位与该页的R位相加
    • 替换时,选择计数器最小的页
  2. Aging 和 LRU 的主要差别
    • 记录下来的历史更短(计数器长度)
    • 无法区分访问的先后顺序(同一个tick内)
  3. Aging需要多少位才够用
    • 实际工作中8位就工作的很好

Working Set

  1. 主要思想
    • 80/20原则
      • 80%的访问只涉及20%的内存空间
      • 80%的访问都来自20%的代码
    • 空间局限性
      • 相邻的页很可能会被访问
    • 时间局限性
      • 被访问的页很可能在不远的将来再被访问
  2. 工作集的概念
    • 工作集被定义为在最近K次访问的那些页
    • 把工作集放在内存能大大地减少缺页
    • 工作集可以近似为过去T秒钟里使用的页
  3. 算法:记录页的"上次访问时间"
    • 在缺页处理时,扫描该进程所有的页
    • 如果R位为1,将该页的上次访问时间设置为当前时间
    • 如果R位为0,计算当前时间和上次访问时间之差
      • 如果差值大于T,则该页在过去T秒里没有被访问过,则替换它
      • 否则,检查下一页
    • 将发生缺页的页加入工作集

WSClock

  1. 将 Working Set类似的组织成环形链表(类似 Clock)
  2. 按表针走动的顺序来检查页
  3. 如果R位为1
    • 将R位置成0,该页的上次访问时间设置为当前时间
    • 检查下一页
  4. 如果R位为0
    • 设差值为 当前时间-上次访问时间
    • 如果该值小于等于T,则代表在过去T秒里被访问过,检查下一页
    • 如果该值大于T,则代表在过去T秒里没有被访问过
      • 若M位为1,则将该页写回加入写回链表(异步进行写回),并检查下一页
      • 若M位为0,则将该页替换出去

总结

  1. LRU很好但是实现困难,Aging是一个很好的近似LFU
  2. Clock被认为是一个很好的实际解决方案
  3. 所有的替换算法都不会优化由Cold Miss带来的缺页
算法 特点
MIN 最优算法,但无法在实际系统中实现
FIFO 没有考虑重复访问的情况,仅根据第一次访问时的顺序进行替换
Second Chance 在FIFO的基础上考虑了重复访问
Clock 基于Second Chance,可以更加高效的实现
LRU 考虑了访问时效性,实现时有频繁链表操作,开销较大
NRU 粗粒度近似LRU,只区分有无访问,同时优先保留脏页;与LRU比,时效性考虑较少
LFU 考虑访问的次数,将访问次数多的数据留在内存中;容易受短期高频访问的影响
Aging 近似LFU,更容易实现;记录窗口短;相同访问次数时,无法区分时效性
Working Set 实现开销大
WSClock 基于WS优化实现时的数据结构(环形链表),实现开销小