物理内存管理:连续内存分配
地址空间定义
- 物理地址空间:硬件支持的地址空间
- 起始地址0,直到 MAXsys
- 逻辑地址空间:在 CPU 运行的进程看到的地址
- 起始地址0,直到 MAXprog
地址生成时机和限制
- 编译时
- 假设起始地址已知
- 如果起始地址改变,必须重新编译
- 加载时
- 如编译时起始位置未知,编译器需生成可重定位的代码(relocatable code)
- 加载时,生成绝对地址
- 执行时
- 执行时代码可移动
- 需地址转换(映射)硬件支持
地址生成过程
- CPU
- ALU:需要逻辑地址的内存内容
- MMU:进行逻辑地址和物理地址的转换
- CPU 控制逻辑:给总线发送物理地址请求
- 内存
- 发送物理地址的内容给 CPU
- 或接受 CPU 数据到物理地址
- 操作系统
- 建立逻辑地址 LA 和物理地址 PA 的映射
连续内存分配和内存碎片
- 连续内存分配
- 给进程分配一块不小于指定大小的连续的物理内存区域
- 内存碎片
- 空闲内存不能被利用
- 外部碎片
- 分配单元之间的未被使用内存
- 内部碎片
- 分配单元内部的未被使用内存
- 取决于分配单元大小是否要取整
连续内存分配:动态分区分配
- 动态分区分配
- 当程序被加载执行时,分配一个进程指定大小可变的分区(块、内存块)
- 分区的地址是连续的
- 操作系统需要维护的数据结构
- 所有进程的已分配分区
- 空闲分区(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 )
- 整个可分配的分区大小 2U
- 需要的分区大小为 2U-1 < s <= 2U 时,把整个块分配给该进程
- 如 s <= 2i-1 ,将大小为 2i 的当前空闲分区划分成两个大小为 2i-1 的空闲分区
- 重复划分过程,直到 2i-1 < s <= 2i ,并把一个空闲分区分配给该进程
伙伴系统的实现
- 数据结构
- 空闲块按大小和起始地址组织成二维数组
- 初始状态:只有一个大小为 2U 的空闲块
- 分配过程
- 由小到大在空闲块数组中找最小的可用空闲块
- 如空闲块过大,对可用空闲块进行二等分,知道得到合适的可用空闲块
- 释放过程
- 把释放的块放入空闲块数组
- 合并满足合并条件的空闲块
- 合并条件
- 大小相同 2i
- 地址相邻
- 起始地址较小的块的起始地址必须是 2^(i+1) 的倍数
物理内存管理:非连续内存分配
非连续分配的设计目标
- 连续分配的缺点
- 分配给程序的物理内存必须连续
- 存在外碎片和内碎片
- 内存分配的动态修改困难
- 内存利用率较低
- 非连续分配的设计目标:提高内存利用效率和管理灵活性
- 允许一个程序的使用非连续的物理地址空间
- 允许共享代码与数据
- 支持动态加载和动态链接
- 非连续分配需要解决的问题
- 如何实现虚拟地址和物理地址的转换?
- 软件实现 (灵活,开销大)
- 硬件实现 (够用,开销小)
- 如何实现虚拟地址和物理地址的转换?
- 非连续分配的硬件辅助机制
- 如何选择非连续分配中的内存分块大小?
- 段式存储管理( segmentation )
- 页式存储管理( paging )
- 如何选择非连续分配中的内存分块大小?
段地址空间
- 进程的段地址空间由多个段组成
- 主代码段
- 子模块代码段
- 公用库代码段
- 堆栈段( stack )
- 堆数据( heap )
- 初始化数据段
- 符号表等
- 段式存储管理的目的
- 更细粒度和灵活的分离与共享
段访问机制
- 段的概念
- 段表示访问方式和存储数据等属性相同的一段地址空间
- 对应一个连续的内存"块"
- 若干个段组成进程逻辑地址空间
- 段访问:逻辑地址由二元组 (s,addr) 表示
- s: 段号
- addr: 段内偏移
页式存储管理
- 页帧(帧,物理页面,Frame,Page Frame)
- 页面(页,逻辑页面,Page)
- 页面到页帧
- 逻辑地址到物理地址的转换
- 页表
- MMU/TLB
帧( Frame )
- 物理内存被划分成大小相等的帧,内存物理地址的表示:二元组 (f,o)
f: 帧号( F 位,共有 2F 个帧)
o: 帧内偏移 ( S 位,每帧有 2S 字节)
物理地址 = f * 2S + o - 基于页帧的物理地址计算实例
- 假定
- 16-bit 的地址空间
- 9-bit (512 byte) 大小的页帧
- 物理地址计算
- 物理地址表示 = (3, 6)
- 物理地址 = f * 2S + o
-
F = 7,S = 9,f = 3,o = 6
- 实际物理地址 = 2^9 * 3 + 6 = 1536 + 6 = 1542
- 假定
页( Page )
- 进程逻辑地址空间被划分为大小相等的页
- 页内偏移 = 帧内偏移
- 通常:页号大小 ≠ 帧号大小
- 进程逻辑地址的表示:二元组 (p, o)
p: 页号 ( P 位,2P 个页)
o: 页内偏移 ( S 位,每页有 2S 字节)
虚拟地址 = p * 2S + o
- 页式存储中的地址映射
- 页到帧的映射
- 逻辑地址中的页号是连续的
- 物理地址中的帧号是不连续的
- 不是所有的页都有对应的帧
页表
- 页表概述
- 页表保存了逻辑地址与物理地址之间的映射关系
- 页表结构
- 每个进程都有一个页表
- 每个页面对应一个页表项
- 随进程运行状态而动态变化
- 页表基址寄存器( PTBR: Page Table Base Register )
- 页表项的组成
- 帧号:f
- 页表项状态
- 存在位( register bit )
- 修改位( dirty bit )
- 引用位( clock/reference bit )
- 每个进程都有一个页表
- 页式存储管理机制的性能问题
- 内存访问性能问题
- 访问一个内存单元需要2次内存访问
- 第一次访问:获取页表项
- 第二次访问:访问数据
- 页表大小问题
- 页表可能非常大
- 64位机器如果每页1024字节,那么一个页表的大小会是多少
- 如何处理?
- 缓存 ( Caching )
- 间接 ( Indirection ) 访问
- 内存访问性能问题
解决页表问题
- 快表 ( Translation Look-aside Buffer, TLB )
- 缓存近期访问的页表项
- TLB 使用关联存储 ( association memory ) 实现,具备快速访问性能
- 如果 TLB 命中,物理页号可以很快被获取
- 如果 TLB 未命中,对应的表项被更新到 TLB 中
- 缓存近期访问的页表项
- 多级页表
- 通过间接引用将页号分成 k 级
- 建立页表"树"
- 减少每级页表的长度
- 通过间接引用将页号分成 k 级
- 反置页表
- 大地址空间问题
- 对于大地址空间 ( 64-bits ) 系统,多级页表变得繁琐
- 比如:5级页表
- 逻辑(虚拟)地址空间增长速度快于物理地址空间
- 页寄存器和反置页面的思路
- 不让页表与逻辑地址空间的大小相对应
- 让页表与物理地址空间的大小相对应
- 对于大地址空间 ( 64-bits ) 系统,多级页表变得繁琐
- 页寄存器( Page Registers )
- 每个帧与一个页寄存器( Page Register )关联,寄存器内容包括:
- 使用位( Register bit ):此帧是否被进程占用
- 占用页号( Occupier ):对应的页号 p
- 保护位 ( Protection bits )
- 页寄存器示例
- 物理内存大小:4096 * 4096 = 4 K * 4 KB = 16 MB
- 页面大小:4096 bytes = 4 KB
- 页帧数:4096 = 4 K
- 页寄存器使用的空间(假设每个页寄存器占8字节):8 * 4096 = 32 Kbytes
- 页寄存器带来的额外开销:32K / 16M = 0.2%(大约)
- 虚拟内存的大小:任意
- 页寄存器方案特征
- 优点
- 页表大小相对于物理内存而言很小
- 页表大小与逻辑地址空间大小无关
- 缺点
- 页表信息对调后,需要依据帧号可找页号
- 在页寄存器中搜索逻辑地址中的页号
- 优点
- 页寄存器中的地址转换
- CPU 生成的逻辑地址如何找对应的物理地址?
- 对逻辑地址进行 Hash 映射,以减少搜索范围
- 需要解决可能的冲突
- 用快表缓存页表项后的页寄存器搜索步骤
- 对逻辑地址进行 Hash 变换
- 在快表中查找对应页表项
- 有冲突时遍历冲突项链表
- 查找失败时,产生异常
- 快表的限制
- 快表的容量限制
- 快表的功耗限制( StrongARM 上快表功耗占 27% )
- CPU 生成的逻辑地址如何找对应的物理地址?
- 每个帧与一个页寄存器( Page Register )关联,寄存器内容包括:
- 反置页表
- 基于 Hash 映射值查找对应页表项中的帧号
- 进程标识与页号的 Hash 值可能有冲突
- 页表项中包括保护位、修改位、访问位和存在位等标识
- 反置页表的 Hash 冲突
- 基于 Hash 映射值查找对应页表项中的帧号
- 大地址空间问题
段页式存储管理
- 需求
- 段式存储在内存保护方面有优势,页式存储在内存利用和优化转移到后备存储方面有优势。
- 段式存储,页式存储能否结合?
- 做法
- 在段式存储管理基础上,给每个段加一级页表
- 段页式存储管理中的内存共享
- 通过指向相同的页表基址,实现进程间的段共享