内存管理
基本概念
- 代码执行三个阶段
- (预处理后)每个代码块编译为机器码
- 链接为具有完善逻辑地址的机器码文件
- 装入内存执行
- 装入的三种方式
- 绝对装入(单道程序):直到装入起始地址的情况下,编译器修正内存地址
- 可重定位装入(早期多道批处理):装入内存时将涉及到地址的操作
- 动态运行时装入(线代操作系统):重定位寄存器
- 内存管理功能
- 内存空间分配回收
- 逻辑扩充内存空间
- 操作系统转换逻辑地址和物理地址
- 操作系统内存保护
- 上下限寄存器
- 重定位寄存器、界地址寄存器判断
交换与覆盖
- 覆盖技术:参照调用结构树,不同时执行的程序片段使用同一块内存分区
- 分配
- 固定区:存放最活跃的代码段,*不会进行调入调出
- 覆盖区(若干):*运行过程中需要根据需要调入调出
- 必须由程序员申明覆盖结构,系统自动完成覆盖
- *对用户不透明,增加编程负担
- 分配
- 交换技术:内存空间紧张时,将进程换出外存(七状态模型)
- 文件系统分为文件区和对换区(swap),对换区连续分配,IO速度更快
- 频繁发生缺页时,换出一些进程;缺页率下降时,暂停换出。
- 优先换出阻塞、换出低优先级进程,为了避免饥饿操作系统还会考虑进程驻留时间
- PCB常驻,不会被换出
内存分配
- 两种碎片
- 内部碎片:分配给进程而进程没有使用
- 外部碎片:分区太小而无法利用
- 拼凑/紧凑技术:移动其他进程内存
- 连续分配
- 单一连续分配
- 内存分为系统区、用户区
- 内存中仅有一道用户程序
- 优点:实现简单、无外部碎片、可使用覆盖技术扩充、不一定需要内存保护(MS-DOS)
- 缺点:只适用于单用户、单任务操作系统、内部碎片
- 固定分区分配
- 用户空间划分为固定大小分区、分区内只装入一道作业
- 分区大小相同:适用于完全相同程序的工业控制
- 分区大小不同:灵活性高,满足各种进程需求
- 使用链表记录分区起始和大小
- 优点:实现简单、无外部碎片
- 缺点:用户程序过大必须使用覆盖技术解决、降低效率、内部碎片
- 动态(可变)分区分配
- 根据用户进程所需内存大小分配刚好合适的内存
- 记录内存使用的数据结构
- 空闲分区表:记录有分区大小、起始地址的数组
- 空闲分区链:存有分区大小、起始地址的双向链表
- 多个分区满足需求时,按照动态分区分配算法选择
- 分区分配与回收
- 分配:调整大小或消除阶段
- 回收:增加分区、合并分区(前有、后有、前后都有)
- 动态分区分配算法
- 首次适应:低址开始,找到第一个装得下的(大进程可能装不下,产生外部碎片)
- 最佳适应:容量递增,找到第一个装得下的(产生很多外部碎片)
- 最坏适应:容量递减,使用第一个(大进程可能装不下)
- 临近适应:地址递增,从上次结束的位置开始查找,使用循环列表,算法开销小(可能导致没有大分区可以用)
- 单一连续分配
基本分页内存
- 页框与页面
- 页框=页帧=内存块=物理块=物理页面(Pageframe):划分的内存空间相等的分区,其编号为页框号=页帧号=内存块号=物理块号=物理页号,0起始
- 页=页面:将进程逻辑地址分为与页框大小相等的部分,编号为页号,0起始
- 操作系统以页框分配内存,页面页框一一对应
- 页面不需要连续存放,可以放入相邻页框
- 页表
- 页表长度M:页表中含有的页表项
- 页表项长度Lp:由内存大小和页框大小决定,需要几位来表示页框号。由于页表也存储在页框中,一般使用恰好可以填满页框的长度
- PCB中用于存储进程逻辑地址与物理内存地址对应关系的数据结构
- 内存块数->推定页表项中块号需要多少字节
- 页表连续存储,不占用空间
- 基本地址变换机构
- 页表寄存器(PTR)记录页表起始地址F和页表长度M(不占有处理机时,存放在PCB中)
- 结构(逻辑地址):
- |0000000011|0000100100|
- | 页号P | 页内偏移W |
- 过程(内存块大小为L,逻辑地址A到物理地址E)
- 逻辑地址A计算(拆分)出页号P=A/L、页内偏移量W=A%L
- 检查页号是否越界P<M(P=M时也会发生越界中断)
- 查询页表F+P*Lp,得到内存块号B
- 得到物理地址(拼接/计算)E=B*L+W
- 访问两次内存
- 快表(TLB:Translation lookaside buffer,联想寄存器)地址变换机构
- 快表是高速缓存(类似Cache,但只存放页表项副本)
- 加速原理
- 先查询快表,是否命中页号
- 命中则直接进行地址变换访存
- 未命中则查询页表,并缓存页表项(如果已满,则使用算法替换旧项),进行地址变换访存
- 快慢表可以同时查找,来节省更多时间
- 局部性原理
- 时间局部性:一定时间内多次查询同一条数据
- 空间局部性:程序访问一个内存很可能访问其周边内存
- 两级页表
- 单级页表问题:
- 页表要求连续存储,但页表可能过长
- 进程页表没必要整体常驻内存
- 页目录表/外层页表/顶层页表
- 建立二级页表块号和其所在内存块号之间的关系
- 注意
- 各级页表大小不能超过一个页面(页面大小/页表项大小)
- n级页表访存n+1次(没有快表机构的情况下)
- 单级页表问题:
基本分段内存
- 段:按照程序自身逻辑划分程序的单位
- 每一个段都有一个段名,0起始编址
- 每个段在内存中占用连续地址空间
- 段号位数决定进程最多的分段
- 段内地址决定每个段的最大长度
- 段表:记录逻辑段在内存中存放位置
- 组成:段号(隐含的)、段长C、基址b
- 段表项长度Ls一致,长度由最大物理地址决定
- 段表寄存器:存储段表基址F,段表长度M
- 结构
- |00000000000|0000000000|
- | 段号S | 段内地址W |
- 转换过程
- 逻辑地址A拆分出段号S、段内地址W
- 检查段号是否越界(S<M,S=M时也会发生越界中断)
- 查询段表F+S*Ls
- 检查段内地址是否越界(W<C,S=C时也会发生越界中断)
- 得到物理地址E=b+W
- 分页与分段的区别
- 页是信息物理单位,对用户不可见;分段是信息逻辑单位,对用户可见
- 分页地址空间一维,只需给出记忆符表示地址;分段地址空间二维,既要给出段名、也要给出段内地址。
- 分段更容易实现信息共享和保护
- 两个进程的段表可以指向同一个段
- 该段中的内容是可重入、纯代码的
- 分段及分页优缺点
- 分页:无外部碎片、少量页内碎片;不方便按照逻辑模块信息共享保护
- 分段:方便按照逻辑模块信息共享保护;段过大很难连续分配空间、产生外部碎片
基本段页式管理
- 先分段、再分页;分段行为用户可见、分页过程用户不可见
- 逻辑地址结构
- |0000000000|000000000|000000000000|
- | 段号S | 页号P | 页内偏移量W |
- 段页式的段表和页表
- 段表(项长度Ls):段号(隐含)、页表长度Mp、页表块号Fp
- 页表(项长度Lp):页号(隐含)、内存块号F
- 段表寄存器:存储段表基址Fs,段表长度Ms
- 转换过程(内存块大小为L,逻辑地址A到物理地址E)
- 逻辑地址A拆分出段号S、页号P、页内偏移量W
- 检查段号是否越界S<Ms
- 查询段表Fs+S*Ls,得到页表长度Mp、页表所在块号Fp
- 检查页号是否越界P<Mp
- 查询页表Fp+P*Lp,得到内存块号B
- 得到物理地址(拼接/计算)E=B*L+W
- 三次访存,可以引入快表机构,实现一次访存
- 二维地址结构
虚拟内存
- 传统存储管理(连续分配、非连续分配)
- 一次性:作业需要一次性全部装入后才能开始运行;导致大作业无法运行、多到程序并发度下降
- 驻留性:作业一旦被装入就会一直驻留到运行结束
- 虚拟内存概念
- 根据局部性原理,将程序很快会用到的部分装入内存、其余驻留外存,即可开始执行
- 访问信息不在内存,操作系统将信息调入内存,并继续执行
- 内存空间不足,操作系统将内存中暂时不用的信息换出到外存
- 用户视角下似乎有比实际内存大的多的内存,即为虚拟内存
- 特性
- 多次性:允许多次调入
- 对换性:无需常驻内存
- 虚拟性:逻辑上扩充了内存
- 实现(结合离散式内存管理,增加请求调页功能)
- 请求分页
- 请求分段
- 请求段页式
请求分页
- 请求页表
- 内存块号
- 状态位:是否调入
- 访问字段:记录最近访问次数或上次访问时间(置换算法参考依据)
- 修改位:是否被修改(决定执行回写还是直接丢弃)
- 外存地址
- 缺页中断机构(在查页表时)
- 当访问的页面不在内存,触发缺页(内)中断,操作系统中断处理程序处理中断,进程被阻塞
- 内存有空闲块,分配空闲块,启动IO操作执行装入、修改状态位
- 内存无空闲块,页面置换算法选择一个页面淘汰,若修改过则回写外存,修改二者状态位,△快表中删除淘汰项
- 与基本分页的细节差异
- 在查询页表时可能执行调页
- 访问页面前,修改快表、访问位、修改位(仅修改时需要)
页面置换算法
- 缺页率:缺页次数/访问次数
- 最佳(OPT)
- 淘汰之后最长时间不再使用的页面
- 由于操作系统无法推断之后要访问的页面序列,是一种理想的算法
- 先进先出(FIFO)
- 最早进入内存的页面
- Belady异常:只有FIFO会产生的一种异常现象,分配内存块增多缺页率却增加,因此FIFO性能差
- 最近最久未使用(LRU)
- 逆向检查当前内存块里有哪些最久未使用,并替换之
- 性能最好,但是开销较大,需要硬件支持
- 时钟置换(CLOCK)最近未用(NRU)
- 简单时钟置换(利用页面置换算法中的访问位)
- 页面访问后访问位置1
- 链接为循环队列,检查队列的访问位,为0则换出,为1则置为0并跳过;最多两轮后一定可以执行置换
- 仅考虑到被淘汰的页面是否被访问过,未修改的页面无需回写外存
- 改进时钟置换
- 考察(访问位,修改位),尽量选择近期没访问过、未修改的页面
- 一轮:找到(0,0)则替换
- 二轮:找到(0,1)则替换,并把访问位置1
- 三轮:找到(0,0)则替换
- 四轮:找到(0,1)则替换
- 考察(访问位,修改位),尽量选择近期没访问过、未修改的页面
- 简单时钟置换(利用页面置换算法中的访问位)
页面分配策略
- 几个概念
- 驻留集:请求分页存储中给进程分配的物理块集合
- 固定分配:运行时驻留集大小不变(只能局部置换)
- 可变分配:运行时驻留集大小可变
- 局部置换:只能选择自己的物理块置换
- 全局置换:可以置换空闲物理块、其他进程的物理块
- 锁定:不允许部分页面换出,如重要的内核数据
- 分配策略
- 固定分配全局置换:灵活度低
- 可变分配全局置换:
- 有空闲则选择空闲物理块换出,无空闲则选择未锁定的页面换出外存
- 只要进程缺页则获得新的物理块,被选中的进程物理块减少、缺页率增加
- 可变分配局部置换:
- 开始分配固定物理块,当进程缺页,只允许在自己的物理块中换出
- 若进程频缺页率高,则系统会增加分配给进程的物理块
- 若进程频缺页率低,则系统会减少分配给进程的物理块
- 调页策略
- 预调页:一次调入若干个页面
- 请求调页:发现缺页才进行调入
- 何处调页
- 足够swap:进程运行前将进程数据从晚间去复制到对换区,运行时在内存与对换区之间调换
- 不足swap:不被修改的都直接从文件区调入
- UNIX:启动从文件区域调入,调出时写入对换区
- 抖动/颠簸现象:由于进程频繁访问的页面数目比可用物理块数多,进程换入的页面马上换出外存,系统效率降低
- 工作集:某个时间窗口内访问页面的集合
- 系统根据工作集大小,判断程序是否有很好的局部性,决定分配的驻留集大小
- 一般情况驻留集≥工作集,否则进程会发生频繁缺页抖动