【操作系统】Paging

539 阅读17分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

注意:下面标星*的表示会在以后补充

Virtual Memory

将用户逻辑内存与物理内存分离,因为只有部分程序需要在内存中才能执行。因此,逻辑地址空间可以比物理地址空间大得多。

实现方法:Paging、Segmentation

所需支持

  • 硬件支持:
    • 内存管理单元 Memory Management Unit(MMU)
    • 转换检测缓冲区 Translation Lookaside Buffer(TLB)
  • 操作系统支持:
    • 用于控制MMU和TLB的虚拟内存系统。

虚拟地址:

  • 虚拟地址是进程用来访问自己的地址空间的内存地址。
  • 当进程访问虚拟地址时,MMU硬件将虚拟地址转换为物理地址
  • 操作系统确定从虚拟地址到物理地址的映射。

MMU 和 TLB:

  • MMU
    • 虚拟地址转换为物理地址的硬件。
    • 每个内存引用都通过MMU传递。
  • TLB
    • MMU虚拟到物理地址转换的缓存
    • 这只是一个用来加速的优化,但很重要!

Paging

物理地址空间被分为大小相同的page frame(页框),与页大小通常相同

操作系统职责

  • 维护页表。
    • 页表是从页转换为帧的数组。
  • 从空闲帧中分配足够的页面以执行程序。
    • 要运行大小为n页的程序,需要找到n个可用页框并加载程序。
  • 操作系统跟踪所有可用帧。
    • 设置页表以将逻辑地址转换为物理地址

优势:

  • 允许进程的物理地址空间不连续,并根据需要进行分配。
  • 易于分配物理内存。
  • 易于“分页”/交换内存块。

问题:内部碎片 Internal Fragmentation

地址转换策略

对于逻辑地址空间2m2^m和页大小2n2^n

image.png

通过页码去页表当中找映射如图,将虚拟地址映射为物理地址(虚拟页码VPN变物理页码PPN)

image.png

页表

OS管理,每个进程一个

Structure of Page Table Entry(PTE)页表项结构

  • 页框编号:映射框编号
  • 存在/不存在位:1/0表示有效/无效条目
  • 保护位:允许什么类型的访问,0表示读/写,1表示只读
  • 修改(脏位):在修改和写入磁盘时设置
  • 引用:设置页面被引用的时间(帮助决定退出哪个页面)
  • 缓存已禁用(禁止位):保证不断从设备读取数据而不是缓存中的副本

image.png

问题和改进方案:

  • 快速访问问题:
    • 通常每个指令需要进行2个或更多页表引用,因此快速访问是必要的
    • 解决:TLB translation look-aside buffer
  • 大内存页表占用问题:
    • 由于内部碎片,每个进程的平均浪费为½页,因此需要较小的页面大小。
    • 但是,小页面->多页面->大页面表->页面表(对于每个进程)将占用大量内存空间
    • 解决:多级页表 Multi-level Page Tables、倒排页表Inverted Page Tables

TLB translation look-aside buffer

通过在转换查找缓冲区(TLB)中缓存最近的映射来消除在页表中遍历查找映射的过程(相当于是映射的Cache),加快映射速度

  • 大多数程序经常引用少量页面
  • TLB是MMU的一部分。
  • 每个TLB条目都是一个页表条目(具有与PTE(page table entry 页表项)相同的元素)。
  • TLB是一种特殊的硬件(称为关联存储器)

针对大内存的页表

多级页表 Multi-level Page Tables

例子:二级页表逻辑地址结构:
通过p1查找一级页表中的映射,从而映射到二级页表
然后再通过p2查找该二级页表中的映射,从而映射到物理页框 image.png

倒排页表 Inverted Page Table

64位地址空间需要多级页表级数过多,内存访问次数过多,因此需要改进。

在物理内存当中,每个页框有一个表项,而不是每个虚拟页一个,这些表项构成倒排页表,再使用TLB加速

线性倒排页表: image.png

hashed倒排页表:

  • 哈希表的输入是PID和VPN(虚拟页码),输出是反向页表的索引。
  • 使用哈希表进行哈希时,可能会发生冲突,可以使用链地址方法解决。
  • 在反向页面表项中添加下一个字段以形成链表(标题的索引在哈希表中)

image.png 比较进程ID和虚拟页码 如果匹配,则找到。  如果不匹配,请在下一个指针中检查另一个页表条目,然后再次检查。

image.png

页面替换算法

发生缺页中断(page fault)往往需要替换页表中的已有表项

页面替换步骤:

  • 1、查找页面在磁盘上的位置。
  • 2、查找空闲页框。
    • 1) 如果有空闲页框,请使用它。
    • 2) 否则,请使用页面替换算法选择victim帧
    • 3) 将所选页面写入磁盘并更新任何必要的表。
  • 3、从磁盘读取请求的页面。
  • 4、重启用户进程。

如何选择victim frame? 希望能替换一个不太有用的页面。  脏页需要write,干净页不需要。  硬件的每个页框都有一个脏位,指示此页面是否已更新。  问题:如何确定效用? 启发式Heuristic:存在时间局部性 删除不太可能再次使用的页面

目标:

  • 实现较低的页面错误率(page fault rate)。(主要目标)
    • 确保大量使用的页面保留在内存中。
    • 替换一段时间内不需要的页面。
  • 减少page fault的延迟。
    • 高效的代码。
    • 替换不需要写出的页面。

The Optimal Algorithm (OPT or MIN) 最优页面替换

选择到下一个引用的时间最长的页面进行替换

其中ABC为引用字符串Reference String,代表被引用的页面序列

image.png 但是因为不可能准确预测下一引用的时间,所以是理想状况

First-in First-Out (FIFO) Algorithm

替换主存中存在时间最长的页(队头),新的页在队尾

image.png Belady anomaly(异常):页面帧越多,页面错误不一定就越少

为什么FIFO可能是好的? 可能很久以前分配的页面不再使用了。 为什么FIFO可能不那么好? 不考虑引用的位置

遭受Belady异常:随着物理内存大小的增加,应用程序的性能可能会变得更差

Second Chance Algorithm

FIFO的变体,寻找最老的未被访问的页面进行替换

发生缺页中断,检查最老页面的R(reference)位:

  • 如果R=0,既老又未被使用,替换掉
  • 如果R=1,把R清0放入链表尾端,重新设置该页的“装入时间”,继续检查队头,直到遇到页面的R=0

image.png

Clock Algorithm

Second Chance Algorithm的更好的实现:

页面保存在环形链表(提高效率),表针指向最老页面

image.png

发生缺页中断,先检查指向的最老页面,如果R=0,替换掉,R=1,把该页的R置0并且移动表针到“第二老”

NRU(Not-Recently-Used)最近未使用

增强second chance,除了R位代表是否被引用,加入一个M(Modified)位代表页是否被修改

发生计时器中断时,定期清除R位(但清除不了M位)

分成4类页,状态转移如下:

image.png

算法:优先找第0类中最老的,如果页表中没有页属于第0类则找第1类,以此类推

优点:易于理解和实施, 性能足够(但不是最佳)。

LRU Least Recently Used 最近最少使用

替换最长时间未使用的页面,区别于NRU的“未使用”,并不区分R和M

image.png

优点:效率比较高,接近OPT。 不会遭受Belady的异常 缺点:实现需要大量硬件,代价高(链表实现、计数器实现)

*LRU实现

NFU Not Frequently Used 最少使用(最不常用)

类似LRU,不同点是没了“最近”,且记录了次数。

  • 每次时钟中断扫描所有页面,如果R=1就在计数器当中加1,然后清除R;
  • 缺页中断时删除计数器值最低(访问次数最少)的页面

问题:可能很久以前某个页被大量使用,但最近不怎么用,不如LRU

Aging Algorithm 老化算法

修饰NFU,在软件上模拟LRU,不同点是这次的粒度granularity是时钟滴答(时钟中断),而不是内存引用次数

实现:

  • 在时钟中断时:
    • 将计数器右移(除以2)
    • 将R位添加到左侧(MSB)
  • 发生缺页中断时,删除计数器最小的(比如一个页面4次时钟中断都未访问,则前4位为0)

image.png

问题:老化算法计数器只有有限位,限制了对以往页面信息记录

Working Set Algorithm

Fetch Policy

什么时候应该将页面加载到主存?

  • 请求分页(demand-paging)策略
    • 页面按需加载,而不是提前加载。
    • 进程启动时,内存中没有加载任何页面;页面错误会将页面加载到内存中。
  • 预分页(pre-paging)策略
    • 在进程运行之前加载页面到工作集页面。
    • 需要工作集,即进程正在使用(当前需要)的页面集合中的页面来加载上下文切换。
  • 很少有系统使用纯按需分页,而是使用预分页

颠簸(Thrashing)

  • 如果一个进程每执行几条指令就发生一次缺页中断,就称这个程序发生颠簸
  • 原因:内存太小(页框太少)无法容纳工作集,产生大量缺页中断
  • 后果:如果进程没有“足够”的页面,则页面错误率非常高。这导致:
    • CPU利用率低。
    • OS认为需要提高多道程序设计的程度。
    • 将向系统中添加另一个进程
  • 解决方案:
    • 工作集:根据需要提供尽可能多的frame
    • 页面故障频率 Page Fault Frequency

工作集 Working Set

如果进程的工作集无法保存在内存中,请不要运行该进程,否则将发生大量缺页中断

工作集是任意时刻t,最近k次内存访问所访问过的页面集。

  • 函数w(k,t)是时间t时工作集的大小。
  • w(k,t)是k的单调非减函数。
  • w(k,t)的极限是有限的

近似工作集:过去τ秒内使用的进程页面集

工作集替换算法

基本思想:出现页面错误时,替换不在工作集中的页面

  • 近似值:放弃倒数k个内存引用的想法,使用执行时间
  • 当前虚拟时间:进程自启动以来实际使用的CPU时间量称为其当前虚拟时间。
  • 进程的近似工作集是它在过去的τ秒虚拟时间内引用的页面集。

实现:

  • 每个时钟滴答中,清除所有R位并记录进程虚拟时间t
  • 出现缺页中断,查找逐出候选项时,请扫描物理内存中进程的所有页面:
    • 如果R==1,则将t存储在PTE的LTU(最近使用时间)中,并清除R。
    • 如果R==0:
      • 如果(t–LTU)>τ,则逐出页面(因为它不在工作集中)
      • 如果(t–LTU)<=τ,则记录最大年龄的页面。年龄=当前虚拟时间–上次使用的时间(即age=t-LRU
  • 如果扫描整个表时没有找到要逐出的候选对象
    • 如果找到一个或多个R=0的页面,则会逐出最长时间(age最大)的页面。
    • 最坏的情况是:没有R=0的页面,随机选择一个页面(最好是干净的页面)。

WS Clock Algorithm\

基于时钟算法的改进工作集算法,使用了M位

以前的WS算法要求在每个页面错误时扫描整个页面表

改进WSClock算法:

  • 所有页面都保存在环形队列中。
  • 随着页面的添加,它们进入到环中。
  • “时钟指针”绕着环前进。
  • 每个条目包含R、M、上次使用时间(LRU)

实现: 出现缺页中断时

  • 如果R=1,则设置R=0并更新“上次使用时间”字段,继续寻找。
  • 如果R=0
    • if age<=τ,则向前移动指针并继续寻找。
    • if age>τ
      • if clean,则清除。
      • 如果脏,则计划磁盘写入(因为该页面被修改过,在磁盘上没有有效副本,避免调度写磁盘操作引起的进程切换),继续寻找。
  • 如果指针一直绕到起点
    • 案例1:至少安排了一次写入,遇到的第一个干净页面被逐出。
    • 案例2:未计划写入(所有页面都在工作集中)
      • 清除任何干净的页面并使用它。
      • 如果不存在干净的页面,则选择当前页面并将其写回磁盘。

小结

最好的实践中可使用的两种是老化和工作集时钟

image.png

分页系统设计问题

Resident Set Management 驻留集管理

操作系统必须决定分配给每个进程的帧数

  • 分配较小的数字允许同时在内存中执行多个进程。
  • 帧太少会导致页面错误率高。
  • 太多的帧/进程导致多道程序设计级别低。
  • 超过某个最低级别后,添加更多帧对页面错误率几乎没有影响。

驻留集:主内存中的一组进程页。

 驻留集管理  替换范围 局部替换 全局替换  驻留集大小 固定分配Fixed Allocation Policy  可变分配

Replacement Scope

发生页面错误时要考虑替换的帧集:

  • 局部替换
    • 仅在分配给出现故障的进程的帧中进行选择。
  • 全局替换
    • 内存中任何未锁定的帧都可以替换
    • 高优先级进程可能能够在自己的帧或低优先级进程的帧中选择帧

优劣对比:

  • 局部替换
    • 可能会不必要地减慢进程,因为使用较少的内存无法替换。
  • 全局替换
    • 进程无法控制自己的页面错误率–还取决于其他进程的分页行为(不稳定的执行时间)。
    • 通常会导致更大的吞吐量。
    • 但这是更常用的方法。

Resident Set Size

为进程分配的帧的数量的策略

固定分配策略:根据某些条件,在加载时为进程分配固定数量的帧

  • 平等分配 Equal allocation
    • 例如,有12416个可用页面帧和10个进程,每个进程得到1241个帧,其余6个进入池中,在出现页面错误时使用。
  • 按比例分配 Proportional allocation
    • 例如:一个300-KB的进程得到的分配是一个10-KB进程的30倍。
  • 优先级分配 Priority allocation
    • 使用流程优先级而非规模的比例分配方案。

可变分配策略: 一个进程的帧数可能随时间而变化。

  • 如果页面错误率较高,则增加。
  • 如果页面错误率非常低,则降低。
  • 需要操作系统开销来评估活动进程的行为。

PFF Page Fault Frequency Algorithm

  • 定义页面错误率的上限U和下限L。
  • 如果故障率高于U,则为进程分配更多帧。
  • 如果故障率<L,则分配更少的帧。
  • 常驻集大小应接近工作集大小W。
  • 如果PFF>U且没有更多可用帧,则暂停进程。添加帧减少帧抖动

image.png

*负载控制

Page Size 页大小

Small Page Size

  • 优势
    • 更少的内部碎片,即更好的内存利用率
    • 对于局部引用来说更好,便于捕获未知
  • 缺点
    • 较大的页表
    • 更多页面错误
    • 读/写页的开销更大
    • I/O 延迟高,吞吐量小

Big Page Size的优缺点相反

页表和内部碎片导致损失的开销(Overhead): s=平均进程大小(字节) p=页面大小(字节) e=页表项大小
Overhead=s×e/p+p/2Overhead = s×e/p + p/2p=2sep=\sqrt{2se}时,开销最小

*分离的指令空间和数据空间

解决地址空间太小的问题,分为I地址空间和D地址空间

Shared Pages 共享页面

共享代码:可以共享只读页面

  • 指令和数据空间分开,易于共享代码。
  • 示例:两个进程通过使用相同的I-space页表和不同的D-space页表共享相同的程序。

共享数据:

  • 在UNIX中,在fork系统调用之后,父级和子级需要共享程序文本和数据。
    • fork时不复制页面
    • 为父进程和子进程提供自己的页表。
    • 让两个进程指向同一组页面
    • 共享数据页必须为只读

Copy on write写入时复制(COW): 如果进程写入物理内存中的frame

  • trap到内核。
  • 内核分配新frame。
  • 复制旧frame。
  • 重新启动写入指令。

*Shared Libraries 共享库

*Implementation Issues for Paging

Segmentation

一维地址空间问题:

  • page挨在一起时,扩缩容复杂
  • 分页是一种“平面”内存模型,这意味着程序员可以看到从地址0开始到某个最大地址的地址。
  • 对于某些应用程序,允许在单个进程中使用完全不同的地址空间可能很有用

为机器提供许多完全独立的地址空间,称为Segment。

特点:

  • 段通常不同时包含不同类型的内容
  • 每个段的长度可以在大小上有所不同,从0到允许的最大值(线性地址序列)。
  • 段长度在执行过程中可能会发生变化,但不会相互影响。
  • 段是一个逻辑实体,程序员知道并将其作为单个逻辑实体使用。

image.png

地址转换

虚拟段编号(s) 偏移量(d)

  • 段表(由操作系统维护)
  • 映射二维物理地址。
  • 虚拟段编号用作段表的索引。
  • 每个表条目都有:
    • base–包含段驻留在内存中的起始物理地址
    • limit–指定段的长度

实现:

  • 当CPU生成逻辑地址时,该地址被发送到MMU。MMU使用逻辑地址的段号作为段表的索引。
  • 将偏移量与段限制limit进行比较,如果偏移量更大,则会生成无效地址错误。
  • 否则,偏移量将加到段base以形成物理地址。

image.png

示例:

  • 使用以下段表,计算由段和偏移组成的逻辑地址的物理地址,如下所示:
  • 段2和偏移247,段4和偏移439
  • 段=2,偏移=247: 从段表中,段2的limit=780,base=2200,由于偏移量247小于限制780, 物理地址=偏移量+段基=247+2200=2447
  • 段=4,偏移=439:从段表中,段4的限值=400,段底值=1650, 由于偏移量大于限制,因此会生成无效地址错误。

image.png

优点:

  • 可以收缩/增长段,大小浮动的段很容易提供
  • 可以为每个段提供自己的保护信息,这是一种比试图保护内存的每一页容易得多的保护方案。
  • 链接程序是一项微不足道的任务。
  • 代码可以在进程之间轻松共享。只需加载一次代码段。同一程序的副本访问同一段

缺点:

  • 程序员必须知道正在使用的内存模型(在汇编级别)。
  • 就像交换系统一样,碎片可能会浪费大量内存。
  • 段可能太大,无法放入物理内存。

Segmentation with Paging

  • 将虚拟地址空间视为任意大小的段(逻辑单元)的集合。
  • 将物理内存视为一系列固定大小的页帧。
  • 段通常大于页框,⇒ 通过分页将逻辑段映射到多个页面框架上。
  • 物理内存只包含一个段的所需页面,而不包含整个段。

进程、段、页结构:

  • 每个进程都需要一个段表
  • 段被划分为若干页,为了跟踪这些页面,将为每个段维护一个页表
  • 段表中的每个条目都指向该段的页表。

地址转换策略

虚拟地址划分:

  • 段编号:用于索引段表,其表项给出该段的页表的起始地址
  • 页编号:用于索引该页表以获取相应的frame编号。
  • 偏移量:用于在frame内定位字段

image.png

段表项 Segment Table Entry

image.png 页表项 Page Table Entry

image.png

映射(虚拟地址到物理地址转换)策略:

  • 段号索引到段表中,该段表生成该段的页表的base address。
  • 对照段的limit检查地址的其余部分(页码和偏移量)。
  • 使用页码索引页表得到页框位置
  • 加上偏移量以获取Word(字)的物理地址。

image.png