连续内存分配
操作系统的内存管理
操作系统的内存管理方式:
- 重定位(relocation)
- 分段(segmentation)
- 分页(paging)
- 虚拟内存(virtual memory)
- 目前多数系统(如 Linux)采用按需页式虚拟存储
- 实现高度依赖硬件
- 与计算机存储架构紧耦合
- MMU (内存管理单元): 处理CPU存储访问请求的硬件
地址空间定义
- 物理地址空间——硬件支持的地址空间:
- 起始地址0,直到
- 逻辑地址空间 — 在CPU运行的进程看到的地址
- 起始地址0,直到
地址检查
- 需要确定进程可访问的合法地址的范围,并确保进程只访问其合法地址。如图8.1 所示,通过两个寄存器即基地址寄存器和界限地址寄存器,可以实现这种保护。基地址寄存器(base register) 含有最小的合法物理内存地址,而界限地址寄存器(limit register) 决定了范围的大小。
- 内存空间保护的实现,是通过CPU 硬件对用户模式所产生的每一个地址与寄存器的地址进行比较来完成的。如用户模式下执行的程序试图访问操作系统内存或其他用户内存,则会陷入到操作系统,并作为致命错误处理(图8 .2)。这种方案防止用户程序(有意或无意地)修改操作系统或其他用户的代码或数据结构。
地址绑定
- 编译时:
- 假设起始地址已知
- 如果起始地址改变,必须重新编译
- 加载时:
- 如编译时起始位置未知,编译器需生成可重定位的代码(relocatable code)
- 加载时,生成绝对地址
- 执行时:
- 执行时代码可移动
- 需地址转换(映射)硬件支持
- 编译时:
地址映射
- 运行时从虚拟地址到物理地址的映射是由被称为内存管理单元(memory-management
unit, MMU) 的硬件设备未完成的。 - 在此,用一个简单的MMU 方案来实现这种映射,这是8. 1.1小节所描述的基地址寄存器方案的推广。基地址寄存器在这里称为重定位寄存器( relocation register)。用户进程所生成的地址在送交内存之前,都将加上重定位寄存器的值(如图8 .4所示)
- 运行时从虚拟地址到物理地址的映射是由被称为内存管理单元(memory-management
连续内存分配
- 连续内存分配:给进程分配一块不小于指定大小的连续的物理内存区域
- 内存碎片:空闲内存不能被利用
- 外部碎片:分配单元之间的未被使用内存
- 内部碎片
- 分配单元内部的未被使用内存
- 取决于分配单元大小是否要取整
连续内存分配:动态分区分配
动态分区分配
- 动态分区分配
- 当程序被加载执行时,分配一个进程指定大小可变的分区(块、内存块)
- 分区的地址是连续的
- 操作系统需要维护的数据结构:
- 所有进程的已分配分区
- 空闲分区(Empty-blocks)
- 动态分区分配策略
- 最先匹配:First-fit
- 最佳匹配:Best-fit
- 最差匹配:Worst-fit
最先匹配(First-Fit Allocation)策略
- 思路:分配n个字节,使用第一个可用的空间比n大的空闲块。
- 原理 & 实现:
- 空闲分区列表按地址顺序排序
- 分配过程时,搜索一个合适的分区
- 释放分区时,检查是否可与临近的空闲分区合并
- 优点:
- 简单
- 在高地址空间有大块的空闲分区
- 缺点:
- 外部碎片
- 分配大块时较慢
最佳匹配(Best-Fit Allocation)策略
- 思路:分配n字节分区时,查找并使用不小于n的最小空闲分区
- 原理 & 实现:
- 空闲分区列表按照大小排序
- 分配时,查找一个合适的分区
- 释放时,查找并且合并临近的空闲分区(如果找到)
- 优点:大部分分配的尺寸较小时,效果很好
- 可避免大的空闲分区被拆分
- 可减小外部碎片的大小
- 相对简单
- 缺点:
- 外部碎片
- 释放分区较慢
- 容易产生很多无用的小碎片
最差匹配(Worst-Fit Allocation)策略
- 思路:分配n字节,使用尺寸不小于n的最大空闲分区
- 原理 & 实现:
- 空闲分区列表按由大到小排序
- 分配时,选最大的分区
- 释放时,检查是否可与临近的空闲分区合并,进行可能的合并,并调整空闲分区列表顺序
- 优点:
- 中等大小的分配较多时,效果最好
- 避免出现太多的小碎片
- 缺点:
- 外部碎片
- 释放分区较慢
- 容易破坏大的空闲分区,因此后续难以分配大的分区
碎片整理
碎片整理:通过调整进程占用的分区位置来减少或避免分区碎片
紧凑(compaction)
- 通过移动分配给进程的内存分区,以合并外部碎片
- 条件:所有的应用程序可动态重定位
交换(Swapping in/out)
- 通过抢占并回收处于等待状态进程的分区,以增大可用内存空间
伙伴系统(Buddy System)
- 整个可分配的分区大小
- 需要的分区大小为
时,把整个块分配给该进程;
- 如 ,将大小为 的当前空闲分区划分成两个大小为 的空闲分区
- 重复划分过程,直到 ,并把一个空闲分区分配给该进程
- 数据结构
- 空闲块按大小和起始地址组织成二维数组
- 初始状态:只有一个大小为 的空闲块
- 分配过程
- 由小到大在空闲块数组中找最小的可用空闲块
- 如空闲块过大,对可用空闲块进行二等分,直到得到合适的可用空闲块
- 释放过程:
- 把释放的块放入空闲块数组
- 合并满足合并条件的空闲块
- 合并条件
- 大小相同
- 地址相邻
- 低地址空闲块起始地址为 的位数
非连续内存分配
非连续内存分配
- 连续分配的缺点:
- 分配给程序的物理内存必须连续
- 存在外碎片和内碎片
- 内存分配的动态修改困难
- 内存利用率较低
- 非连续分配的设计目标:提高内存利用效率和管理灵活性
- 允许一个程序的使用非连续的物理地址空间
- 允许共享代码与数据
- 支持动态加载和动态链接
- 非连续分配需要解决的问题:如何实现虚拟地址和物理地址的转换
- 软件实现(灵活,开销大)
- 硬件实现(够用,开销小)
- 非连续分配的硬件辅助机制:如何选择非连续分配中的内存分块大小
- 段式存储管理(segmentation)
- 页式存储管理(paging)
段式存储管理(segmentation)
- 进程的段地址空间由多个段组成:
- 主代码段
- 子模块代码段
- 公用库代码段
- 堆栈段(stack)
- 堆数据(heap)
- 初始化数据段
- 符号表等
- 段式存储管理的目的:更细粒度和灵活的分离与共享
- 段访问机制
- 段的概念:
- 段表示访问方式和存储数据等属性相同的一段地址空间
- 对应一个连续的内存“块”
- 若干个段组成进程逻辑地址空间
- 段访问:逻辑地址由二元组(s, addr)表示
- s:段号
- addr:段内偏移
- 段的概念:
段访问的硬件实现:
页式存储管理(paging)
- 页帧(帧、物理页面, Frame, Page Frame)
- 把物理地址空间划分为大小相同的基本分配单位,内存物理地址的表示:二元组
(f, o) ,f:帧号(F位,共有 个帧);o:帧内偏移(S位,每帧有 字节) - 物理地址 =
- 把物理地址空间划分为大小相同的基本分配单位,内存物理地址的表示:二元组
- 页面(页、逻辑页面, Page)
- 把逻辑地址空间也划分为相同大小的基本分配单位
- 帧和页的大小必须是相同的,页内偏移 = 帧内偏移,通常,页号大小 ≠ 帧号大小
- 页面到页帧
- 逻辑地址到物理地址的转换
- 逻辑地址中的页号是连续的
- 物理地址中的帧号是不连续的
- 不是所有的页都有对应的帧
页表
- 页表结构:
- 每个进程都有一个页表
- 每个页面对应一个页表项
- 随进程运行状态而动态变化
- 页表项的组成:
- 帧号:f
- 页表项标志:
- 存在位(resident bit)
- 修改位(dirty bit)
- 引用位(clock/reference bit)
- 内存访问性能问题
- 访问一个内存单元需要2次内存访问
- 第一次访问:获取页表项
- 第二次访问:访问数据
- 页表大小问题:
- 页表可能非常大
- 如何处理?
- 缓存(Caching)
- 间接(Indirection)访问
快表(Translation Look-aside Buffer, TLB)
- 缓存近期访问的页表项
- TLB 使用关联存储(associative memory)实现,具备快速访问性能
- 如果TLB命中,物理页号可以很快被获取
- 如果TLB未命中,对应的表项被更新到TLB中
- 多级页表:通过间接引用将页号分成k级
大地址空间问题
- 对于大地址空间(64-bits)系统,多级页表变得繁琐
- 逻辑 (虚拟) 地址空间增长速度快于物理地址空间
- 页寄存器和反置页面的思路
- 不让页表与逻辑地址空间的大小相对应
- 让页表与物理地址空间的大小相对应
- 页寄存器(Page Registers)
- 每个帧与一个页寄存器(Page Register)关联,寄存器内容包括:
- 使用位(Residence bit):此帧是否被进程占用
- 占用页号(Occupier):对应的页号p
- 保护位(Protection bits)
- 页寄存器示例
- 物理内存大小:4096*4096=4K*4KB=16 MB
- 页面大小:4096 bytes = 4KB
- 页帧数:4096 = 4K
- 页寄存器使用的空间 (假设每个页寄存器占8字节):8*4096=32 Kbytes
- 页寄存器带来的额外开销:32K/16M = 0.2% (大约)
- 虚拟内存的大小:任意
- 优点:
- 页表大小相对于物理内存而言很小
- 页表大小与逻辑地址空间大小无关
- 缺点:
- 页表信息对调后,需要依据帧号可找页号
- 在页寄存器中搜索逻辑地址中的页号
- 每个帧与一个页寄存器(Page Register)关联,寄存器内容包括:
- 页寄存器中的地址转换
- CPU生成的逻辑地址如何找对应的物理地址?
- 对逻辑地址进行Hash映射,以减少搜索范围
- 需要解决可能的冲突
- 用快表缓存页表项后的页寄存器搜索步骤
- 对逻辑地址进行Hash变换
- 在快表中查找对应页表项
- 有冲突时遍历冲突项链表
- 查找失败时,产生异常
- CPU生成的逻辑地址如何找对应的物理地址?
- 反置页表
- 基于Hash映射值查找对应页表项中的帧号
- 进程标识与页号的Hash值可能有冲突
- 页表项中包括保护位、修改位、访问位和存在位等标识
- 基于Hash映射值查找对应页表项中的帧号
段页式存储管理
- 段式存储在内存保护方面有优势,页式存储在内存利用和优化转移到后备存储方面有优势。
- 段页式存储管理:在段式存储管理基础上,给每个段加一级页表
- 段页式存储管理中的内存共享
- 通过指向相同的页表基址,实现进程间的段共享