3 物理内存管理
目录
内存分层结构
内存管理要求
- 抽象:抽象成逻辑地址空间
- 保护:为应用程序分配独立的地址空间
- 共享:应用程序间需要能够进行数据传递
- 虚拟:将部分硬盘空间作为虚拟内存,存放暂时不使用的应用程序数据
在OS中管理内存的方法
实现高度依赖于硬件,需要知道内存的体系架构
- 程序重定向
- 分段
- 分页
- 虚拟内存
- 按需分配虚拟内存
地址空间
含义
- 物理地址空间:硬件支持的地址空间(起始地址 0 到 地址MAXsys)
- 逻辑地址空间:一个运行的程序具有的内存范围(起始地址 0 到地址MAXprog)
逻辑地址生成
![image-20200705105901824](https://gitee.com/dreameagle0/picture-bed/raw/master/img/image-20200705105901824.png)
地址空间的访问过程
- OS方面
- 建立逻辑地址和物理地址之间的映射
- CPU方面
- 第一步 CPU的ALU需要逻辑地址的内存内容
- 第二步 从内存管理单元MMU(Memory Management Unit)寻找逻辑地址和物理地址之间的映射关系,如果自身找不到就到内存中的map找
- 第三步 控制器从总线发送物理地址的内存内容请求
- 内存方面
- 内存发送物理地址内存内容给CPU
地址空间的隔离与保护
- OS管理每个应用程序运行访问的地址范围,包括起始地址(基址)和地址空间长度
- 如果程序访问的地址超出允许的范围则报错,否则正常访问相应物理地址
连续空间分配
场景
-
为 获得许可的程序 分配运行所需的空间
-
当程序访问数据时,为数据分配相应的连续存储空间
内存碎片问题
- 含义:不能被利用的空闲内存
- 分类:
- 外部碎片:分配空间之间无法利用的内存
- 内部碎片:已分配空间内部无法被使用的内存
简单的内存分配算法
-
第一适配(First Fit):按顺序查找,将第一个大小满足内存分配请求的空闲空间用于分配
- 需求:
- 按地址排序的空闲块列表
- 分配需要寻找一个合适的分区
- 重分配需要检查,看自由分区是否可以和空闲分区合并
- 优势:
- 简单
- 在地址空间的结尾,易于产生更大的空闲块
- 劣势:
- 易产生外部碎片
- 不确定性
- 需求:
-
最优适配(best fit):寻找所有空闲块中最适合分配请求的空闲块(差值最小)
- 基本原理:
- 避免分割空闲块
- 最小化外部碎片尺寸
- 需求:
- (按照尺寸)对需要的空闲块列表进行排序
- 分配需要寻找一个最合适的分区
- 重分配需要搜索及合并相邻的空闲分区
- 优势:
- 当大部分分配是小尺寸时非常有效
- 比较简单
- 劣势:
- 外部碎片比较多且小,往往难以被利用
- 重分配慢
- 易产生很多没用的微小外部碎片
- 基本原理:
-
最差匹配(worst fit):寻找满足分配请求的最大空闲空间
- 避免产生太多微小碎片
- 需求:
- 需要对空闲块按尺寸大小排序
- 分配很快(获得最大分区)
- 重分配需要合并相邻空闲块。若有,调整空闲块列表
- 优势
- 假如分配的是中等尺寸,效果最好
- 劣势
- 重分配慢
- 外部碎片
- 易于破碎大的空闲块以致大分区无法被分配
内存碎片整理
- 压缩式碎片整理:通过移动已分配的内存空间,以合并空闲空间(孔洞),使其构成连续的空闲空间
- 什么时候移动?
- 程序正在运行的时候不能移动,否则访问的地址可能发生错位
- 程序处于等待状态时可以移动
- 移动的时间开销:频繁移动会造成时间开销过大
- 什么时候移动?
- 交换式碎片整理:通过将内存中部分处于等待状态的程序内存空间转移到硬盘上的虚拟空间,以腾出足够的空闲空间
- 运行程序需要更多的内存
- 抢占等待的程序 & 回收它们的内存
- 哪些程序交换?
- 什么时候换入换出?
非连续内存分配
为什么需要非连续内存分配
- 连续内存分配的缺点
- 分配给一个程序的物理内存是连续的
- 容易产生内、外碎片
- 内存利用率较低
- 非连续内存分配优点
- 分配给一个程序的物理内存是非连续的
- 更好的内存的利用与管理
- 允许共享代码和数据(共享库等)
- 允许动态加载和动态链接
- 非连续内存分配缺点
- 如何建立逻辑地址和物理地址的转换
- 通过软件实现:时间开销大
- 通过硬件实现:分段(目前用的较少)和分页
- 如何建立逻辑地址和物理地址的转换
分段(Segmentation)
-
原理:一个应用程序内部分成多个独立的部分,如程序主程序、子程序、数据段、堆栈、库等等,通过将这几部分分开,各自分配内存,实现非连续的内存分配,更好的分离和共享
-
段访问机制
-
一个段 —— 一个内存块、一个逻辑地址空间
-
程序访问内存地址需要一个2维的二元组(s段号, addr段内偏移)
-
-
硬件实现方案
- 程序运行前,OS设置段表
- CPU根据段号查询段表,获取段基址和偏移量
- CPU检查逻辑地址偏移量是否在limit之内,是则加上基址访问内存,否则进行异常处理
分页(Paging)
-
与分段的主要区别:分段的段大小是可变的,分页的页大小(页内偏移)是固定的
优点:页大小固定,便于内存管理 -
原理:
- 建立物理内存至固定大小的帧(frame)——大小是2的幂次,如512bytes、4k、8k等
- 建立逻辑地址空间至相同大小的页(page)
- 建立逻辑地址空间与物理内存地址的映射关系(pages to frames)
- 页表(page table)
- CPU中的内存管理单元MMU / 快表TLB
-
帧
- 一个内存的物理地址分为二元组 (f, o)
- f -- 帧号frame number(F位,共2F帧)
- o -- 帧内偏移offset(S位,每帧2S字节)
- 物理地址 = 2S x f + o
- 一个内存的物理地址分为二元组 (f, o)
-
页
- 页内偏移大小 = 帧内偏移大小,但页号大小(页数) 不一定等于 帧号大小(帧数)(即2P可能大于2F,页——连续的虚拟内存,映射到帧——非连续的物理内存,页号大小可能大于帧号大小,对应到虚拟内存)
- 一个逻辑地址是一个二元组 (p, o)
- p -- 页号page number(P位,共2P个页)
- o -- 页内偏移offset(S位,每页2S 个字节)
- 逻辑地址 = 2S x p + o
-
页的寻址机制
- OS在开启分页机制前建立页表
- CPU寻址时,通过逻辑地址的页号作为index查询页表,获取相应的帧号,以此计算实际物理地址
页表
-
概述
-
每个运行的程序都会有一个页表
-
页表项包含标志位(flags,如对应的帧是否存在等)和对应的帧号
-
-
存在的问题:
- 访问一个内存单元需要2次内存访问(访问页表 + 访问数据)
- 页表空间占用
- 页表访问时间效率
- 页表可能非常大(CPU放不下)
- 解决思路
- 缓存(Caching)→ TLB
- 间接(Indirection)访问 → 多级页表
- 访问一个内存单元需要2次内存访问(访问页表 + 访问数据)
-
快表TLB(Translation Look-aside Buffer)-- 减小时间开销
-
缓存近期访问的页帧转换表项
-
TLB使用关联内存(相关存储器)实现,访问速度块,可以并发地进行访问
-
如果TLB命中,物理页号可以很快被获取
-
如果TLB未命中,对应的表项被更新到TLB中(x86中由硬件完成,mips由OS完成)
-
-
二级页表(可推广到多级页表) -- 以时间换空间,减小空间开销
- 将逻辑地址中的page number细分成p1、p2
- p1对应一级页表的index,页表项给出帧号所在二级页表的偏移量
- p2对应二级页表的index,页表项给出对应的帧号
- 对于p1中flags中标识了不存在对应物理内存地址的部分,p2中可以将相应的部分可以删除。
而若只采用一级页表,即使映射的物理内存不存在,也必须存储 - 访问一次内存,需要访问两次页表,时间开销增大了,但是空间开销减小了
- 可以结合TLB减小时间开销
- 将逻辑地址中的page number细分成p1、p2
-
反向页表
-
传统页表缺陷:
- 页表大小与逻辑地址空间关联很大,对于大地址空间(如64bit),前向映射页表很大、繁琐(如5级页表)
- 能否使页表大小不与逻辑地址空间相对应,而与物理地址空间对应? --> 反向页表
-
含义:使页表与物理地址空间对应,帧号作为index,页号放在页表项,以减小页表大小
- 问题:实际需要通过页号查找帧号,怎么办? 页寄存器 → 关联存储器 → 哈希查找
-
基于页寄存器(Page Registers)的方案
- 通过页寄存器存储页表,帧号做Index,页号连同flags作为页表项
-
基于关联存储器(associative memory)的方案
- 类似TLB的方案,可并行的通过页号查找帧号
- 缺陷:存储器无法做得太大,而且一般放在CPU中。即使放到CPU外,又会造成二次访问页表的问题
-
基于哈希查找的方案
- 建立哈希表 H(PID, p),通过哈希函数由页号计算页寄存器索引
- 缺陷:
- 哈希碰撞(一个input,多个output)
- 哈希表放在内存中,需考虑二次访问时间开销,可结合TLB
-