操作系统—连续内存分配 & 非连续内存分配
1. 连续内存分配
内存碎片问题
分区的动态分配
简单的内存管理方法:
-
当一个程序准许运行在内存中时,分配一个连续的区间
-
分配一个连续的内存区间给运行的程序以访问数据
分区的动态分配方式有以下三种 :
- 第一适配:在内存中找到第一个比需求大的空闲块,分配给应用程序
- 最佳适配:在内存中找到最小、最适合的空闲块,分配给应用程序
- 最差适配:在内存中找到最大的空闲块,分配给应用程序
最先匹配(First Fit Allocation)策略
原理 & 实现
- 按地址顺序排序的空闲块列表
- 分配需要寻找一个合适的分区
- 重分配需要检查 是否自由分区可以合并于相邻的空闲分区(若有)
优点
- 简单
- 易于产生更大空闲块,在高地址空间有大块的空闲分区
缺点

- 外部碎片
- 不确定性
- 分配大块时较慢
最佳匹配(Best Fit Allocation)策略
为了避免分割大空闲块,为了最小化外部碎片产生的尺寸。
原理 & 实现
- 按尺寸排序的空闲块列表
- 分配需要寻找一个合适的分区
- 释放时,查找并且合并临近的空闲分区(如果找到)
优点
- 大部分分配的尺寸较小时,效果很好
- 可避免大的空闲分区被拆分
- 可减小外部碎片的大小
- 相对简单
缺点
- 外部碎片
- 释放分区较慢
- 容易产生很多无用的微小碎片
最差匹配(Worst Fit Allocation)策略
为了避免有太多微小的碎片。
原理 & 实现
- 按尺寸排序的空闲块列表
- 分配很快(获得最大的分区)
- 释放时,检查是否可与临近的空闲分区合并,进行可能的合并,并调整空闲分区列表顺序
优点
- 中等大小的分配较多时,效果最好
- 避免出现太多的小碎片
缺点
- 外部碎片
- 释放分区较慢
- 容易破坏大的空闲分区,因此后续难以分配大的分区
分配方式的区别
分配方式 | 第一适配分配 | 最优适配分配 | 最差适配分配 |
---|---|---|---|
原理 & 实现 |
1. 按地址排序的空闲块列表 2. 分配需要寻找一个合适的分区 3. 重分配需要检查是否可以合并相邻空闲分区 |
1. 按尺寸排序的空闲块列表 2. 分配需要寻找一个合适的分区 3. 重分配需要检查是否可以合并相邻空闲分区 |
1. 按尺寸排序的空闲块列表 2. 分配差距最大的分区 3. 重分配需要检查是否可以合并相邻空闲分区 |
优势 | 简单 / 易于产生更大空闲块 | 比较简单 / 大部分分配是小尺寸时高效 | 分配很快 / 大部分分配是中尺寸时高效 |
劣势 | 产生外部碎片 / 不确定性 | 产生外部碎片 / 重分配慢 / 产生很多没用的微小碎片 | 产生外部碎片 / 重分配慢 / 易于破碎大的空闲块以致大分区无法被分配 |
三种分配方式并无优劣之分,因为我们无法判断内存请求的大小。
碎片整理
可以看到的是,三种分区动态分配的方式都会产生外部碎片,因此我们可以对碎片进行一定的整理来解决碎片问题。
压缩式碎片整理
紧凑(compaction)
-
重置程序以合并孔洞(碎片)
-
要求所有程序是 动态可重置的
-
问题 :
➢ 何时重置?(在程序处于等待状态时才可以重置)
➢ 开销(内存拷贝:重定位)
交换式碎片整理
分区对换(Swapping in/out)
- 运行程序需要更多的内存时,抢占等待的程序并回收它们的内存,以增大可用内存空间
-
问题 :
➢ 哪些程序应该被回收?
伙伴系统(Bubby System)
伙伴系统比较好的折中了分配和回收过程当中 合并和分配块位置的 碎片问题。
伙伴系统的实现
伙伴系统中的内存分配
目前在我们用到的Linux、 Unix都有 Buddy System 的实现,它是用来做内核里的存储分配。
既然选择了连续,就避免不了产生碎片。
2. 非连续内存分配
连续内存分配的缺点
- 分配给一个程序的物理内存是连续的
- 内存利用率较低
- 有外碎片 / 内碎片的问题
能否通过新的一些手段来改善这些情况?=> 非连续内存分配。
非连续内存分配的优点
-
一个程序的物理地址空间是非连续的
-
更好的内存利用和管理
-
允许共享代码与数据(共享库等…)
-
支持动态加载和动态链接
非连续内存分配的缺点
建立虚拟地址和物理地址的转换难度大
-
软件方案(开销相当大)
-
硬件方案(采用硬件辅助机制)
➢ 分段(Segmentation)
➢ 分页(Paging)
分段(Segmentation)
- 程序的分段地址空间
- 分段寻址方案
-
段: 计算机程序是由各个
段
组成的:➢ 在代码执行方面会看到有主程序、子程序,还有共享的一些库 形成代码不同的分段
➢ 数据方面:栈段、堆段 还有一些共享的数据段
➢ 不同的段之间有不同的属性
-
分段: 更好的分离和共享
程序的分段地址空间如下图所示 :
把左边运行程序的逻辑地址空间看成一个连续一维的线性数组,通过段机制的映射关系把不同的内存块(如:代码、数据、堆、栈)分别映射到不同的内存中的段,可以看到映射到物理地址空间后位置不一样,变得不连续了。
分段寻址方案
段表 在寻址之前操作系统就会建立好,之后段机制就可以正常工作了。
分页(Paging)
- 分页地址空间
- 页寻址方案
页式存储管理
页帧(帧、物理页面,Frame,Page Frame)
- 把物理地址空间划分为大小相同的基本分配单位(帧)
- 大小是2的幂,e.g.,512 / 4096 / 8192
页面(页、逻辑页面,Page)
- 把逻辑地址空间也划分为相同大小的基本分配单位(页)
- 大小是2的幂,e.g.,512 / 4096 / 8192
- 帧和页的大小必须是相同的
页面到页帧(pages to frames → 建立方案)
- 转换逻辑地址为物理地址
- 页表
- MMU / TLB
帧(Frame)
frame-number
:帧号off-set
:帧内偏移
例子 : 16 − b i t 16-bit 16−bit 地址空间, 9 − b i t ( 512 b y t e ) 9-bit(512 byte) 9−bit(512byte) 大小的页帧 物理地址 = ( 3 , 6 ) (3, 6) (3,6) 物理地址 = 2 9 ∗ 3 + 6 = 1542 2^9 * 3 + 6 = 1542 29∗3+6=1542
分页和分段的最大区别 : 这里的 S 是一个固定的数,而分段中的长度限制不定
页(Page)
page-number
:页号(跟帧号可能不同)off-set
:页内偏移(跟帧内偏移一样)
页寻址机制
页式存储中的地址映射
一般情况逻辑地址空间要大于物理地址空间。
映射到物理地址空间 存放是不连续的,带来的好处是有助于减少碎片的产生。
页表(Page Table)
页表结构
其实就是一个大数组,它的索引 i n d e x index index 指的是 p a g e − n u m b e r page-number page−number(页号),索引对应的内容是 f r a m e − n u m b e r frame-number frame−number(帧号),得到帧号后再叠加上 o f f − s e t off-set off−set 得到物理地址。
还有一些内容: b i t bit bit,比如 r e s i d e n t b i t resident \ bit resident bit 表示这个页表项是否合法(对应在物理地址中是否存在), 0 0 0 代表不合法, 1 1 1 代表合法。
地址转换的实例
分页机制的性能问题
空间代价、时间开销
-
访问一个内存单元需要2次内存访问
➢ 一次用于获取页表项
➢ 一次用于访问数据
-
页表可能非常大
➢ 64位机器如果每页1024字节,那么一个页表的大小会是多少?( 2 64 / 2 10 = 2 54 2^{64} / 2^{10} = 2^{54} 264/210=254 放不下)
➢ 每一个运行的程序都需要有一个页表
-
如何处理?
➢ 缓存(Caching)
➢ 间接(Indirection)访问
快表TLB:解决时间问题
快表(Translation Look-aside Buffer,TLB)
Translation Look-aside Buffer(TLB)是一个缓冲区,CPU中的MMU(内存管理单元)都有一个 快表 TLB 的特殊区域。
TLB表项本身是由一种快速查询的存储器:相关存储器 实现的,它的速度很快,可以并发的进行查找,但容量是有限的;可以将经常访问的页表项存放在这里,提升访问速度;
当CPU得到一个逻辑地址的时候,首先会去TLB中查,如果查到这个key => p
则直接返回对应的value => f
;没查到再去页表中查。
二级 / 多级页表:解决空间问题
时间换空间
二级页表
- 将页号分为两个部分,页表分为两个,一级页号对应一级页表,二级页号对应二级页表。
- 一级页号查表获得在二级页表的起始地址,地址加上二级页号的,在二级页表中获得帧号
- 节约了一定的空间,在一级页表中如果 r e s i d e n t b i t = 0 resident bit = 0 residentbit=0,可以使得在二级页表中不存储相关 i n d e x index index,而只有一张页表的话,这一些 i n d e x index index 都需要保留。
多级页表
反向页表(inverted page table)
有没有一种方法使得页表大小与 逻辑地址大小 没有那么大的关系,尽量与 物理地址大小 建立对应关系?——反向页表
基于页寄存器(Page Registers)的方案
存储(帧号,页号)使得表大小与物理内存大小相关,而与逻辑内存关联减小。
那就需要下面的方案来实现:
基于关联内存(associative memory)的方案
就是在上方TLB中提到的相关存储器,并行查找,效率很快。
设计成本太大,硬件逻辑复杂,容量不大,需要放置在CPU中。
基于哈希(hash)查找的方案
- 在哈希函数的基础上再加一个参数 P I D PID PID,标记当前运行程序的编号,根据 h ( P I D , p ) h(PID,p) h(PID,p) 作为 i n p u t input input 来设计出一个比较简洁的哈希函数,算出它对应的 f r a m e − n u m b e r frame-number frame−number。
- 这种方式可以有效缓解映射的开销,需要硬件帮助,但会出现哈希冲突,用上面的 P I D PID PID 解决。
这种方式还是需要把反向页表放到内存中,做哈希计算时也需要到内存中取值,内存的开销还是很大,所以还需要有一个类似TLB的机制缓存起来,降低访问的时间。
目前来说,这种机制只在高端CPU中存在,好处:
- 容量可以做的很小,只和物理空间关联
- 对于之前的结构,每一个运行程序都需要有一个 p a g e t a b l e page \ table page table,但对于这种反向结构,整个系统只需要一个,因为它用的是物理内存的
帧号
作为 i n d e x index index 而建的表,而这个表与我们有多少个进程无关,所以它占的空间节省很多;但它是有代价的,它需要以一种很高速的哈希计算、硬件处理机制、高校函数以及解决冲突的机制才可以使访问的效率得到保障。 - 这种机制 有硬件、相应的操作系统软件配合 可以在空间和时间上取得比较好的结果。
段页式存储管理的需求
- 段式存储在内存保护方面有优势,页式存储在内存利用和优化转移到后备存储方面有优势。
- 段式存储、页式存储能否结合?
段页式存储管理
段页式存储管理中的内存共享
整理自 【清华大学】 操作系统