本文我们将介绍以下三部分内容:
1.连续内存分配
2.非连续内存分配
3.页表
1.连续内存分配
1)内存碎片问题
内存碎片问题指的是空闲的内存无法被利用
- 外部碎片 : 分配单元间的未使用内存
- 内部碎片 : 分配单元内的未使用内存
2)分区的动态分配
当一个程序准许运行在内存中时,分配一个连续的区间以访问数据。
分区的动态分配方式有以下三种 :
- 第一匹配分配 : 在内存中找到第一个比需求大的空闲块, 分配给应用程序
- 最优适配分配 : 在内存中找到符合要求的最小的空闲块, 分配给应用程序
- 最差适配分配 : 在内存中找到符号要求的最大的空闲块, 分配给应用程序
第一匹配算法
最佳匹配算法
最差匹配算法
分配方式的区别
| 分配方式 | 第一匹配分配 | 最优适配分配 | 最差适配分配 |
|---|---|---|---|
| 分配方式实现需求 | 1. 按地址排序的空闲块列表 2. 分配需要寻找一个合适的分区 3. 重分配需要检查是否可以合并相邻空闲分区 | 1. 按尺寸排序的空闲块列表 2. 分配需要寻找一个合适的分区 3. 重分配需要检查是否可以合并相邻空闲分区 | 1. 按尺寸排序的空闲块列表 2. 分配最大的分区 3. 重分配需要检查是否可以合并相邻空闲分区,然后调整空闲块列表 |
| 优势 | 简单 / 易于产生更大空闲块 | 比较简单 / 大部分分配是小尺寸时高效 | 分配很快 / 大部分分配是中尺寸时高效 |
| 劣势 | 产生外部碎片 / 不确定性 | 产生外部碎片 / 重分配慢 / 产生很多没用的微小碎片 | 产生外部碎片 / 重分配慢 / 易于破碎大的空闲块以致大分区无法被分配 |
3)碎片整理
可以看到的是,三种分区动态分配的方式都会产生外部碎片,因此我们可以对碎片进行一定的整理来解决碎片问题。
- 压缩式碎片整理
- 重置程序以合并碎片
- 要求所有程序是动态可重置的
- 问题 :
- 何时重置 ? (在程序处于等待状态时才可以重置)
- 需要考虑内存拷贝的开销
- 交换式碎片整理
- 运行多个程序内存不足时,将抢占等待的程序并将其内存进行回收,将其相关数据存放到磁盘中
- 问题 :
- 选择哪个程序交换至硬盘中 ?
- 何时做这个换入/换出的操作?(若程序较大,其中的开销)
2.非连续内存分配
本小节介绍的非连续内存分配可以有效的减少碎片的出现。
1)非连续内存分配的必要性
连续内存分配的缺点
- 分配给一个程序的物理内存是连续的
- 内存利用率低
- 有外碎片 / 内碎片的问题
非连续内存分配的优点
- 一个程序的物理地址空间是非连续的
- 更好的内存利用和管理
- 允许共享代码与数据(共享库等...)
- 支持动态加载和动态链接
非连续内存分配的难点
- 如何建立虚拟地址和物理地址的转换
- 软件方案
- 硬件方案(采用硬件方案) : 分段 / 分页
2)分段(Segmentation)
主要存在两个问题:1.程序的分段地址空间;2.分段寻址方案
段 : 在程序中会有来自不同文件的函数 ; 在程序执行时, 不同的数据也有不同的字段, 比如 : 堆 / 栈 / .bss / .data 等
分段: 更好的分离和共享
程序的分段地址空间如下图所示 :
那么逻辑地址空间到物理地址空间的映射如何实现呢?
- 如果软件实现的话,开销会很大;需要使用硬件来实现分段寻址。
分段寻址方案(段访问机制)
逻辑地址空间连续,但是物理地址空间不连续,使用映射机制进行关联.
一个段 : 一个内存"块"
程序访问内存地址需要 : 一个二维的二元组(s, addr)
- s---分段号
- addr---段内偏移
操作系统维护一张段表, 存储**(段号, 物理地址中的起始地址, 长度限制)**
物理地址 : 段表中的起始地址 + 二元组中的偏移地址
硬件实现方案
- 分段号被映射到段表。由于每个段的大小不一致,会进行安全检查,将段的限制与偏移进行比较。 如果偏移量小于极限值,则地址有效,否则由于地址无效而引发错误。
- 在有效地址的情况下,该段的基地址(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)
计算示例
页(Page)
- 一个程序的逻辑地址空间被划分为大小相等的页. 页内偏移的大小 = 帧内偏移的大小
- 页号大小 <> 帧号大小
一个逻辑地址是一个二元组(p, o) → (页号, 页内偏移)
页号 : P位, 共有2^P个页
页内偏移 : S位, 每页有2^S个字节
虚拟地址 = 2^S * p + o
页寻址方案
操作系统维护一张页表, 页表保存了逻辑地址——物理地址之间的映射关系
存储 : (页号, 帧号)
- 逻辑地址空间应当大于物理内存空间
- 页表:页映射到帧(可通过页号得知帧号)
- 页是连续的虚拟内存
- 帧是非连续的物理内存(有助于减少碎片的产生)
- 不是所有的页都有对应的帧
3.页表、TLB
本节我们将介绍1.页表概述;2.转换后备缓冲区(TLB);3.二级/多级页表;4.反向页表
1)页表概述
每一个运行的程序都有一个页表,并且页表是由操作系统建立起来的
- 属于程序运行状态, 会动态变化
PTBR: 页表基址寄存器
**地址转换的实例**
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可能是由操作系统实现)
4)二级/多级页表
时间(页表级数⬆访问开销⬆)换空间
二级页表
- 顶级页表虽然只有一个页面但是可以存放1K个页表项,其中每一个页表项对应的是下一级的1K个页表项。所以可以存放的最大空间是1K* 1K *4KB = 4GB内存。
- 将页号分为两个部分, 页表分为两个, 一级页号对应一级页表, 二级页号对应二级页表.
- 一级页号查表获得在二级页表的起始地址, 起始地址加上下表中p2的值, 在二级页表中获得帧号
优点
- 使一些不存在映射关系的页表项没必要占用内存了,节约了一定的空间, 在一级页表中如果resident bit = 0, 可以使得在二级页表中不存储相关index,而只有一张页表的话, 这一些index都需要保留
多级页表
5)反向页表
解决大地址空间问题。帧号为索引,页号为内容。实际查找还是用页号去查帧号
目的 : 根据帧号获得页号【以帧号为索引,存放的是页号】
反向页表只需要存在一张即可
-
有大地址空间(64-bits), 前向映射页表变得繁琐. 比如 : 使用了5级页表
-
不是让页表与逻辑地址空间的大小相对应, 而是让页表与物理地址空间的大小相对应.
逻辑地址空间增长速度快于物理地址空间
基于页寄存器的方案
- 这种方案使得寄存器的容量只与物理地址的大小相关,与逻辑地址大小无关。
- 问题:我们如何找到page number 的位置,我们只是得到了以frame number为索引的数组。
每一个帧和一个寄存器关联, 寄存器内容包括 :
- 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)的方案
硬件设计复杂, 容量不大, 需要放置在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, 如果包含, 则代表成功, 否则失败
声明:
- 本文仅用作个人学习笔记。
- 若有侵权,立即删除。