【课程笔记】4.操作系统如何管理物理内存

251 阅读10分钟

本文我们将介绍以下三部分内容:

1.连续内存分配

2.非连续内存分配

3.页表

1.连续内存分配

1)内存碎片问题

内存碎片问题指的是空闲的内存无法被利用

  • 外部碎片 : 分配单元间的未使用内存
  • 内部碎片 : 分配单元内的未使用内存

image-20230111113653773.png

2)分区的动态分配

当一个程序准许运行在内存中时,分配一个连续的区间以访问数据。

分区的动态分配方式有以下三种 :

  1. 第一匹配分配 : 在内存中找到第一个比需求大的空闲块, 分配给应用程序
  2. 最优适配分配 : 在内存中找到符合要求的最小的空闲块, 分配给应用程序
  3. 最差适配分配 : 在内存中找到符号要求的最大的空闲块, 分配给应用程序

第一匹配算法

image-20230111115627700.png

最佳匹配算法

image-20230111115921134.png

最差匹配算法

image-20230111121407317.png

分配方式的区别

分配方式第一匹配分配最优适配分配最差适配分配
分配方式实现需求1. 按地址排序的空闲块列表 2. 分配需要寻找一个合适的分区 3. 重分配需要检查是否可以合并相邻空闲分区1. 按尺寸排序的空闲块列表 2. 分配需要寻找一个合适的分区 3. 重分配需要检查是否可以合并相邻空闲分区1. 按尺寸排序的空闲块列表 2. 分配最大的分区 3. 重分配需要检查是否可以合并相邻空闲分区,然后调整空闲块列表
优势简单 / 易于产生更大空闲块比较简单 / 大部分分配是小尺寸时高效分配很快 / 大部分分配是中尺寸时高效
劣势产生外部碎片 / 不确定性产生外部碎片 / 重分配慢 / 产生很多没用的微小碎片产生外部碎片 / 重分配慢 / 易于破碎大的空闲块以致大分区无法被分配

3)碎片整理

可以看到的是,三种分区动态分配的方式都会产生外部碎片,因此我们可以对碎片进行一定的整理来解决碎片问题。

  1. 压缩式碎片整理
    • 重置程序以合并碎片
    • 要求所有程序是动态可重置的
    • 问题 :
      • 何时重置 ? (在程序处于等待状态时才可以重置)
      • 需要考虑内存拷贝的开销

image-20230111141536559.png

  1. 交换式碎片整理
    • 运行多个程序内存不足时,将抢占等待的程序并将其内存进行回收,将其相关数据存放到磁盘中
    • 问题 :
      • 选择哪个程序交换至硬盘中 ?
      • 何时做这个换入/换出的操作?(若程序较大,其中的开销)

image-20230112222536892.png

2.非连续内存分配

本小节介绍的非连续内存分配可以有效的减少碎片的出现。

1)非连续内存分配的必要性

连续内存分配的缺点

  1. 分配给一个程序的物理内存是连续的
  2. 内存利用率低
  3. 有外碎片 / 内碎片的问题

非连续内存分配的优点

  1. 一个程序的物理地址空间是非连续的
  2. 更好的内存利用和管理
  3. 允许共享代码与数据(共享库等...)
  4. 支持动态加载和动态链接

非连续内存分配的难点

  • 如何建立虚拟地址和物理地址的转换
    • 软件方案
    • 硬件方案(采用硬件方案) : 分段 / 分页

2)分段(Segmentation)

主要存在两个问题:1.程序的分段地址空间;2.分段寻址方案

段 : 在程序中会有来自不同文件的函数 ; 在程序执行时, 不同的数据也有不同的字段, 比如 : 堆 / 栈 / .bss / .data 等

image-20230112201543398.png

分段: 更好的分离和共享

程序的分段地址空间如下图所示 :

image-20230112201720576.png

那么逻辑地址空间到物理地址空间的映射如何实现呢?

  • 如果软件实现的话,开销会很大;需要使用硬件来实现分段寻址。

分段寻址方案(段访问机制)

逻辑地址空间连续,但是物理地址空间不连续,使用映射机制进行关联.

一个 : 一个内存"块"

程序访问内存地址需要 : 一个二维的二元组(s, addr)

  • s---分段号
  • addr---段内偏移

操作系统维护一张段表, 存储**(段号, 物理地址中的起始地址, 长度限制)**

物理地址 : 段表中的起始地址 + 二元组中的偏移地址

image-20230112202259225.png

硬件实现方案

image-20230112205740231.png

  • 分段号被映射到段表。由于每个段的大小不一致,会进行安全检查,将段的限制与偏移进行比较。 如果偏移量小于极限值,则地址有效,否则由于地址无效而引发错误。
  • 在有效地址的情况下,该段的基地址(Base)被添加到偏移量(Limit)以获得主存储器中实际字的物理地址。

3)分页(Paging)

主要存在两个问题:1.分页地址空间;2.页寻址方案

划分物理内存至固定大小的帧(Frame[物理帧])

  • 大小是2的幂,e.g. 512 / 4096 / 8192

划分逻辑地址空间至相同大小的页(Page[逻辑页])

  • 大小是2的幂, e.g.512 / 4096 / 8192

建立方案 → 转换逻辑地址为物理地址(pages to frames)

  • 页表
  • MMU / TLB

帧(Frame)

image-20230112210403156.png

计算示例

image-20230112212416684.png

页(Page)

  • 一个程序的逻辑地址空间被划分为大小相等的页. 页内偏移的大小 = 帧内偏移的大小
  • 页号大小 <> 帧号大小

一个逻辑地址是一个二元组(p, o) → (页号, 页内偏移)

页号 : P位, 共有2^P个页

页内偏移 : S位, 每页有2^S个字节

虚拟地址 = 2^S * p + o

页寻址方案

操作系统维护一张页表, 页表保存了逻辑地址——物理地址之间的映射关系

存储 : (页号, 帧号)

  • 逻辑地址空间应当大于物理内存空间
    • 页表:页映射到帧(可通过页号得知帧号)
  • 页是连续的虚拟内存
  • 帧是非连续的物理内存(有助于减少碎片的产生)
  • 不是所有的页都有对应的帧

image-20230112213335367.png

3.页表、TLB

本节我们将介绍1.页表概述;2.转换后备缓冲区(TLB);3.二级/多级页表;4.反向页表

1)页表概述

每一个运行的程序都有一个页表,并且页表是由操作系统建立起来的

  • 属于程序运行状态, 会动态变化
  • PTBR : 页表基址寄存器

image-20230115201021658.png

**地址转换的实例**

image-20230115204231837.png

2)分页机制的性能问题

问题:访问一个内存单元需要2次内存访问(页就存放在内存中)

  • 一次用于获取页表项
  • 一次用于访问数据

页表可能非常大

  • 64位机器如果每页1024字节, 那么一个页表的大小会是多少?(2^64 / 2^10 = 2^54 存放不下)
  • 每一个运行的程序都需要有一个页表

如何处理?

  • 缓存(Caching)
  • 间接(Indirection)访问

3)转换后备缓冲区(TLB)

缓解时间问题Translation Look-aside Buffer(TLB) 是一个缓冲区. CPU中有快表TLB(可以将经常访问的页表存放在这边)

缓存近期访问的页帧转换表项

  • TLB使用关联内存实现, 具备快速访问性能
  • 如果TLB命中, 物理页号可以很快被获取
  • 如果TLB未命中, 会去查询页表,如果能够查到,再将对应的表项更新到TLB中(更新操作在x86的CPU中由硬件实现, 其他的CPU可能是由操作系统实现)

image-20230115204912252.png

4)二级/多级页表

时间(页表级数⬆访问开销⬆)换空间

二级页表

  • 顶级页表虽然只有一个页面但是可以存放1K个页表项,其中每一个页表项对应的是下一级的1K个页表项。所以可以存放的最大空间是1K* 1K *4KB = 4GB内存。
  • 将页号分为两个部分, 页表分为两个, 一级页号对应一级页表, 二级页号对应二级页表.
  • 一级页号查表获得在二级页表的起始地址, 起始地址加上下表中p2的值, 在二级页表中获得帧号

优点

  • 使一些不存在映射关系的页表项没必要占用内存了,节约了一定的空间, 在一级页表中如果resident bit = 0, 可以使得在二级页表中不存储相关index,而只有一张页表的话, 这一些index都需要保留

image-20230115205639216.png

多级页表

image-20230115212616564.png

5)反向页表

解决大地址空间问题。帧号为索引,页号为内容。实际查找还是用页号去查帧号

目的 : 根据帧号获得页号【以帧号为索引,存放的是页号】

反向页表只需要存在一张即可

  • 有大地址空间(64-bits), 前向映射页表变得繁琐. 比如 : 使用了5级页表

  • 不是让页表与逻辑地址空间的大小相对应, 而是让页表与物理地址空间的大小相对应.

    逻辑地址空间增长速度快于物理地址空间

基于页寄存器的方案

  • 这种方案使得寄存器的容量只与物理地址的大小相关,与逻辑地址大小无关。
  • 问题:我们如何找到page number 的位置,我们只是得到了以frame number为索引的数组。

image-20230115214050503.png

每一个帧和一个寄存器关联, 寄存器内容包括 :

  • resident bit : 此帧是否被占用
  • occupier : 对应的页号 p
  • protection bits : 保护位

实例 :

  • 物理内存大小是 : 4096 * 4096 = 4K * 4KB = 16 MB
  • 页面大小是 : 4096 bytes = 4 KB
  • 页帧数 : 4096 = 4 K
  • 页寄存器使用的空间(假设8 bytes / register) : 8 * 4096 = 32 Kbytes
  • 页寄存器带来的额外开销 : 32K / 16M = 0.2%
  • 虚拟内存大小 : 任意

优势 :

  • 转换表的大小相对于物理内存来说很小
  • 转换表的大小跟逻辑地址空间的大小无关

劣势 :

  • 需要的信息对调了, 那么如何再根据帧号可以找到页号
  • 如何转换回来? (如何根据页号找到帧号)
  • 在需要在反向页表中搜索想要的页号

基于关联内存(associative memory)的方案

image-20230115214634612.png

硬件设计复杂, 容量不大, 需要放置在CPU中

  • 如果帧数较少, 页寄存器可以被放置在关联内存中
  • 在关联内存中查找逻辑页号
    • 成功 : 帧号被提取
    • 失败 : 页错误异常 (page fault)
  • 限制因素:
    • 大量的关联内存非常昂贵(难以在单个时钟周期内完成 ; 耗电)

基于哈希(hash)的方案

实现:表1:key:帧号;value:页号;表2:key:页号+PID;value:帧号

  • 表2的页号通过表1来实现,比如我知道了帧号1对应001页,属于PID5,那就建立hash(001+5)得到唯一的帧号1

哈希函数 : h(PID, p) 从 PID 标号获得页号【PID缓解hash冲突】

在反向页表中通过哈希算法来搜索一个页对应的帧号

  • 对页号做哈希计算, 为了在帧表中获取对应的帧号
  • 页 i 被放置在表 f(i) 位置, 其中 f 是设定的哈希函数
  • 为了查找页 i , 执行下列操作 :
    • 计算哈希函数 f(i) 并且使用它作为页寄存器表的索引,
    • 获取对应的页寄存器
    • 检查寄存器标签是否包含 i, 如果包含, 则代表成功, 否则失败

image-20230115215022572.png

声明

  • 本文仅用作个人学习笔记。
  • 若有侵权,立即删除。