操作系统-第八章-内存管理

背景知识

  • 背景:内存是现代计算机运行的核心。内存由一个很大的字节数组来组成,每个字节都有各自的地址。CPU根据程序计数器的值从内存中提取指令,这些指令可能引起对特定内存地址的额外加载与存储

硬件基础

  • 程序必须装入内存才能被执行
  • CPU可以直接访问的存储器只有主存高速缓存寄存器
  • 寄存器通常可在1个(或少于1个)CPU时钟周期内完成访问,完成主存访问可能需要多个CPU时钟周期
  • CPU暂停(Stall):在读取内存数据时,CPU空闲
  • Cache位于主存和CPU寄存器之间,协调速度差异
  • 内存保护需要保证正确的操作
  • 单道程序的内存
  • 多道程序的内存


内存管理目的和功能

  • 目的
    • 提高内存利用率
    • 提高指令执行速度
    • 保证指令安全运行
  • 功能
    • 内存分配
    • 内存回收
    • 地址转换
    • 存储保护
    • 内存共享

逻辑地址和物理地址

  • 逻辑地址
    • 也称:虚拟地址、程序地址
    • 由CPU产生
    • 在进程内的相对地址
    • 逻辑地址空间:由程序所生成的所有逻辑地址的集合
  • 物理地址
    • 也称:绝对地址、实地址
    • 内存地址
    • 所有内存同一编址
    • 物理地址空间:逻辑地址空间对应的所有物理地址的集合


独立运行内存空间

  • 需要确保每个进程都有一个单独的内存空间。单独的进程内存空间可以保护进程而不互相影响,这对将多个进程加到内存以便并发执行来说至关重要。
  • 内存空间保护的实现是通过CPU硬件对在用户模式下产生的地址与寄存器的地址进行比较来完成的
  • 只有操作系统可以通过特殊的特权指令,才能加载基地址寄存器和界限地址寄存器,这种方案允许操作系统修改这两个寄存器的值,而不允许用户程序修改他们
  • 在内核模式下执行的操作系统可以无限制地访问操作系统及用户地内存
  • 基址寄存器:进程最小的合法物理内存地址
  • 界限寄存器:进程地址的长度
  • CPU在执行指令时,需要进行地址合法性验证


地址绑定

指令和数据绑定到内存

  • 程序以二进制可执行文件的形式存储在磁盘上,为了执行,程序被调入内存并放在程序空间内
  • 地址绑定(重定位):在程序装入内存时,把程序中的相对地址转换为内存中的绝对地址的过程
  • 指令和数据绑定到内存地址可在三个不同阶段:
    • 编译时期
      • 如果内存位置已知,可生成绝对代码(absolute code)
      • 如果开始位置改变,需要重新编译代码
    • 加载时器
      • 如果存储位置在编译时不知,则必须生成可重定位代码( relocatable code),绑定会延迟到加载时进行
    • 执行时期
      • 如果进程执行时可在内存移动,则地址绑定可延迟到执行时
      • 需要硬件对地址映射的支持(例如基址和界限寄存器)
  • 在这些步骤中,地址可能会有不同表示形式
    • 源程序中的地址通常是用符号表示(如变量count)
    • 编译器通常将这些符号地址绑定到可重定位的地址
    • 链接程序或加载程序再将这些可重定位的地址绑定到绝对地址
    • 每次绑定都是从一个地址空间到另一个地址空间的映射

内存管理单元(MMU)

  • 内存管理单元是把虚拟地址映射到物理地址的硬件
  • 是CPU用来管理内存的控制线路
  • 在MMU策略中,基址寄存器中的值在其送入内存的时候被加上重定位寄存器的值
  • 用户程序对应到的是逻辑地址,绝不会看到真正的物理地址
  • 只有当它作为内存地址时,它才会相对于基地址寄存器进行重定位
  • 用户程序处理逻辑地址
  • 内存映射硬件将逻辑地址转变为物理地址


动态加载

  • 例程在调用之前并不加载
  • 优点
    • 更好的内存空间利用率
    • 没有被使用的例程不被载入
    • 当需大量代码来处理不经常使用的功能时非常有用
  • 不需要操作系统的特别支持,通过程序设计实现
  • Windows 的动态链接库


动态链接

  • 链接:将各种代码和数据片段收集并组合成为一个单一文件的过程
  • 动态链接:那些组成程序的目标文件等到程序要运行时才进行链接
    • 和各种库文件的链接被推迟到执行时期
    • 需要动态装载技术支持
    • 操作系统需要检查程序是否在进程的内存空间,所以需要操作系统支持
  • 这一技术通常用于系统库,如语言子程序库,可以减少磁盘空间和内存空间
  • 可用于库的更新
  • 一小段代码 - 存根,用来定位合适的驻留在内存中的库程序
    • 存根用程序地址来替换自己,并开始执行程序
  • 例子
    • 动态链接技术的实现依赖于动态装载和动态绑定
    • sum需要动态装载装入进来,需要动态绑定技术找到物理地址
    • 动态绑定:逻辑地址到物理地址的绑定
    • 动态链接:符号名到地址的转换

交换

  • 一个进程可以暂时被交换(swap)到内存外的一个备份区 ,随后可以被换回内存继续执行
  • 备份区———是一个固定的足够大的可以容纳所有用户内存映 像拷贝的快速磁盘;必须提供对这些内存映像的直接访问
  • 滚入,滚出(Roll out, roll in)————交换由于基于优先级的算法而不同,低优先级的进程被换出,这样高优先级的进程可以被装入和执行
  • 交换时间的主要部分是转移时间,总的转移时间直接同交换的内存的数量成比例
  • 交换较为耗时(100MB大约4s)
  • 交换技术在现代操作系统中一般很少使用
  • 常用策略:当空闲内存不够时采用交换


连续内存分配

概念

  • 连续内存分配:为一个用户程序分配一个连续的内存空间
    • 早期内存分配模式,运用于内存较少系统
  • 分类
    • 单一连续分配
    • 固定分区分配
    • 可变分区分配
  • 主存通常被分为两部分
    • 操作系统(通常驻留在低端,因为中断矢量保存在低端)
      • 操作系统可以位于内存低端,也可位于高端,影响这一决定的主要因素是中断向量的位置,由于中断向量通常保存在内存低端,因此操作系统通常也驻留在内存低端
    • 用户进程(保存在内存高端)

单一连续分配

  • 分配方式:单道程序环境下,仅装有一道用户程序,即整个内存的用户空间由该程序独占
    • 内存分配管理十分简单,内存利用率低
    • 用于单用户、单任务OS
  • 未采取存储器保护措施
    • 节省硬件
    • 方案可行,又不影响系统安全性

固定分区分配

  • 最早、最简单的可行多道程序的内存管理方式
  • 分区:预先把可分配的内存空间分割成若干个连续区域
  • 每个分区的大小可以相同也可以不同
  • 分区大小固定不变,每个分区装一个且只能装一个程序
  • 内存分配:,如果有一个空闲分区,则分配给进程;进程运行结束时,其分区被收回,重新分配各其他进程
  • 分区大小一样
    • 缺乏灵活性(程序太小:浪费内存;程序太大:装不下)
    • 有些场合适用,如利用一台计算机同时控制多个相同对象
  • 分区大小不等
    • 多个小分区
    • 适量中分区
    • 少量大分区

可变分区分配

  • 是固定分区方案的延伸,主要用于批处理系统
  • 分区(孔,Hole):可用的内存块,不同大小的分区分布在整个内存中
  • 根据进程的需要,动态的分配内存空间,即当一个进程到来的时候,它将从一个足够容纳它分区中分配内存,分区中未分配的内存仍然是可用的,可以下次再使用
  • 操作系统包含以下信息:
    • 已分配的分区
    • 空的分区-空闲分区表

存储分配算法

  • 首次适应(First-fit):分配先找到的合适的分区
  • 最佳适应(Best-fit):搜索整个序列,找到适合条件的小的分区进行分配
  • 最差适应(Worst-fit):搜索整个序列,寻找大的分区进行分配
  • 在速度和存储空间的利用上,首次适应和佳适应要好于差适应。首次适应法和佳适应法在空间上利用差不多,但首次适应法更快些。

回收方法

  • a.回收内存块前后无空闲块
  • b.回收内存块前有后无空闲块
  • c.回收内存块前无后有空闲块
  • d.回收内存块前后均有空闲块


碎片

  • 外碎片:整个可用内存空间可以用来满足一个请求,但它不是连续的
    • 首次适应法和佳适应法都有这个问题,这个问题可能很严重,坏的情况下,每两个进程之间都有空闲块被浪费
    • 属于系统
  • 内碎片:分配的内存可能比申请的内存大一点,外碎片和内碎片两者之间的差别是内碎片在分区内部,但又不被使用
    • 属于进程
  • 可通过紧缩来减少外碎片
    • 把一些小的空闲内存结合成一个大的块
    • 只有重定位是动态的时候,并且在运行时,才有可能进行紧缩
    • 简单的合并算法是简单地将所有进程移动到内存的一端,所有的空闲分区移动到另一端,这种方法开销较大。为减少开销,可以选择移动内容小的一种
    • 另一种方案:允许进程的逻辑地址空间是不连续的;这样,只要物理内存可用,就允许为进程分配内存

分页

  • 分页内存管理方案————现代操作系统常用方案
  • 需要操作系统和计算机硬件的协作

基本方法

  • 进程逻辑地址空间可能不连续
    • 如果有可用的物理内存,它将分给进程
  • :把物理内存分成大小固定的块
    • 大小为2的幂,根据计算机结构不同大小不同
    • 早期:512字节至8K字节
    • 现在:4K-64K字节
  • :把逻辑内存也分位固定大小的块
  • 页大小(与帧大小一样)是由硬件来决定的
  • 系统保留所有空闲帧的记录
  • 运行一个有N页大小程序,需要找到N个空帧来装入程序
  • 建立一个页表,把逻辑地址转换为物理地址
  • 分页本身是一种动态的重定位
    • 每个逻辑地址由分页硬件绑定为某个物理地址
    • 采用分页类似于采用一组基址(重定位)寄存器,每个基址对应着一个内存帧
  • 不会产生外碎片:每个空闲帧都可以分配给需要它的进程
  • 存在内碎片
  • 分页内存系统的物理内存的大小不同于进程的最大逻辑大小
    • 当进一步探索分页时,将引入其他的信息,这个消息应保存在页表条目中
    • 该信息也减少了可用于帧地址的位数
    • 因此,一个具有32位页表条目的系统可访问的物理内存可能小于最大值
  • 分页的一个重要方面是,程序员视图的内存和实际的物理内存的清楚分离
  • 操作系统通过帧表数据结构管理物理内存,在帧表中,每个条目对应着一个帧,以表示该帧是空闲还是已占用;如果占用,是被那个(哪些)进程的那个页所占用
  • 逻辑内存和物理内存的分页地址


地址转换机制

  • 分页地址被分为:
    • 页号:包含每个页在物理内存中的基址,用来作为页表的索引
    • 页偏移:同基址相结合,用来确定送入内存设备的物理内存地址
  • 线性地址和二维地址


页表的实现

  • 页表被保存在主存中
  • 页表基址寄存器(PTBR)指向页表
  • 页表限长寄存器(PRLR)表明页表的长度
  • 在这个机制中,每一次的数据/指令存取需要两次内存存取,一次是存取页表,一次是存取数据/指令
  • 解决两次存取的问题,是采用小但专用且快速的硬件缓冲,这种缓冲称为转换表缓冲器(TLB)或联想寄存器
  • TLB是一个硬件功能,因此操作系统及其设计师似乎不必关心。但是设计师需要了解TLB的功能和特性,它们因硬件平台的不同而不同
  • 硬件功能对内存性能有着显著的影响,而操作系统的改进(如分页)能导致硬件的改进并反过来受其影响
  • 联想寄存器:并行查找
    • 地址转换 (A´, A´´) ;如果A´在联想寄存器中,把帧号取出来 ;否则从内存中的页表中取出帧号
  • 有效访问时间(EAT):
    • 联想寄存器的查找需要时间单位a微秒
    • 假设内存一次存取需要b微秒
    • 命中率———在联想寄存器中找到页号的百分比,比率与联想寄存器的大小有关
    • 命中率 = λ
    • EAT = λ (a + b) + (1 – λ) (a+2b)

内存保护

  • 简单方法:
    • 把页号和页表限长寄存器(PRLR)比较
  • 内存的保护由与每个帧相连的保护位来实现
  • 有效-无效位附在页表的每个表项中:
    • 有效表示相关的页在进程的逻辑地址空间,并且是一个合法的页
    • 无效表示页不在进程的逻辑地址空间中


页共享

  • 共享代码
    • 如果代码是可重入代码(只读),可以在进程间共享(如文本编辑器, 编译器, 数据库系统)
    • 共享代码必须出现在所有进程的逻辑地址空间的相同位置
  • 私有代码和数据
    • 每个进程保留一个代码和数据副本
    • 存有私有数据和代码的页能够出现在逻辑地址空间的任意位置


页表结构

    • 32位逻辑地址
    • 页大小为4KB(也就是212
    • 那么一个页表多可包含1M(232/212=220)个表项
    • 假设每个页表项占用4个字节,那么每个进程需要4MB物理空间,也就是1024个连续页面来存储页表
    • 需要这么多个连续页面来存放页表不一定能实现
  • 解决方法
    • 层次页表
    • 哈希页表
    • 反向页表

层次页表

  • 两级分页
    • 一个逻辑地址被分为(在4K页大小的32位系统上):
      • 一个20位的页号
      • 一个12位的页偏移
    • 由于页表所在页也被分页,页号被进一步分为:
      • 一个10位的页号
      • 一个10位的页偏移
    • 因此,逻辑地址表示如下:
    • 地址转换机制:
      • 由于地址转换由外向内,这种方案也称为向前映射页表
  • 可依据实际情况进行多次分页
  • 若页表划分为N级,则需要访问内存N+1次
  • 如系统中存在快表,则在快表命中时,只需要访问一次内存即可

哈希页表

  • 通常地址空间大于32位
  • 采用虚拟页码作为哈希值
  • 哈希页表的每一个条目都包括一个链表,该链表的元素哈希到同一位置(该链表用来解决处理碰撞)
  • 每个元素由三个字段组成:虚拟页码映射的帧码指向链表内下一个元素的指针
  • 虚拟页号与链表中的每个元素相比较,找到匹配项。如果匹配,则相应的物理帧被取出


反向页表

  • 对于每个正真的内存页或帧才有一个条目
  • 每个条目保存在正真内存位置的页的虚拟地址,以及包括拥有这个页的进程的信息
  • 反向页表的条目中需要一个地址空间标识符,以确保一个特定进程的一个逻辑页可以映射到相应的物理帧
  • 讨论
    • 减少了需要储存每个页表的内存,但是当访问一个页时,增加了寻找页表需要的时间
    • 使用哈希表来将查找限制在一个或少数几个页表条目
    • 实现共享内存困难,每个物理页只有一个虚拟页条目,所以一个物理页不可能有两个或更多的共享虚拟地址
      • 解决方案:只允许页表包含一个虚拟地址到共享物理地址的映射,这意味着,对未映射的虚拟地址的引用会导致页错误

分段

  • 分页内存管理存在的问题
    • 用户视角的内存和实际物理内存的分离,即用户视角的内存与实际物理地址不一样
    • 没有用户愿意将内存看作一个线性字节数值,程序员通常愿意将内存看成一组长度不同的段的集合,段是逻辑上有意义的单位,而且段与段之间没有一定的顺序
  • 存在外碎片的可能性小,段小可用分配到小内存

基本方法

  • 支持用户观点的内存管理机制
  • 一个程序是一些段的集合,一个段是一个逻辑单位
  • 逻辑地址空间是由一组段组成的,每个段都有名称和长度,地址指明了段名称段内偏移
  • 分段逻辑地址是二维,物理地址是一维
  • 一个逻辑地址是两个向量的集合
    • <segment-number, offset>
  • 通常,在编译用户程序时,编译器会根据输入程序来自动构造段
  • 在编译时链接的库可能分配不同的段。加载程序会装入所有这些段,并为它们分配段号
  • 分段的逻辑视图:


分段机制-1

  • 一个逻辑地址是两个向量的集合
    • <segment-number, offset>
  • 段表(segment table)——映射二维物理地址,每个表项包括:
    • 基址base —— 包括内存中段物理地址的起始地址
    • 限长limit —— 指定段的长度
  • 段表基址寄存器(STBR)指向段表在内存中的地址
  • 段表限长寄存器(STLR)表明被一个程序所使用的段的数目
    • 如果s<STLR,段号s是合法的
  • 分段硬件:
  • 分段例子


分段机制-2

  • 由于段的长度各不相同,因此这种内存分配实际上是一个动态存储分配问题,可采用可变分区方案实现
  • 内存分配:
    • 首先/佳适应法
    • 外碎片问题
  • 重定位
    • 动态
    • 由段表来执行
  • 共享
    • 共享的段
    • 同样的段号
    • 段是逻辑上有意义的单位
    • 实现更方便
    • 比页共享更容易实现
  • 分段共享例子


分段机制-3

  • 保护,每个段表的表项有
    • 有效位 = 0 ⇒ 非法段
    • 读/写/执行权利
  • 保护位同段相联系,在段的级别进行代码共享

段页式

原理

  • 分段和分页原理的结合
  • 基本原理:先将用户程序分成若干段,并为每个段赋予一个段号,再把每个段号分成若干个页
  • 逻辑地址:<段号,页号,页内偏移>
  • 从用户视角看,程序被划分成了逻辑上有意义的段
  • 从系统角度看,物理内存被划分成了固定大小的帧
  • 存在内碎片
  • 无外碎片

例子

  • Intel386的段页式存储管理
  • Intel 32和64-bit CPU段页式结构
    • 32位:IA-32 (64位:IA-64)
    • 支持分段和段页式
      • 每段最大:4GB
      • 每个进程最多:16K段
        • 分成2部分
        • 第一部分:大8K个段是进程私有(保存在local descriptor table (LDT))
        • 第二部分:第二部分:大8K个段私有进程共享(保存在global descriptor table (GDT))
  • IA-32地址转换
  • Intel IA-32分段结构
  • Intel IA-32分页结构


内存“扩充”技术

  • 内存空间不足怎么办?-解决方法:
    • 紧缩Compaction(可变分区)
    • 覆盖技术Overlaying
    • 交换技术Swapping
    • 虚拟内存Virtual Memory

覆盖

  • 待解决问题:程序大小超过物理内存总和
  • 程序执行时:
    • 只在内存中保留哪些在任何时间都需要的指令和数据
    • 程序的不同部分在内存中相互替换
    • 其他部分根据逻辑将不会同时执行的程序段,共享同一块内存区域
  • 优点:由程序员声明覆盖结构,不需要操作系统的特别支持
  • 缺点:覆盖结构的程序设计很复杂
  • 应用于早期的操作系统
  • 例:


交换

  • 在多道程序环境下:
    • 一方面,在内存中的某些进程由于某事件尚未发生而被阻塞运行,但它却占用了大量的内存空间,甚至有时可能出现在内存中所有进程都被阻塞而迫使CPU停止下来等待的情况
    • 另一方面,却又有着许多作业在外存上等待,因无内存而不能进入内存运行的情况
    • 浪费资源,降低系统吞吐量
  • 一个进程可以暂时被交换(swap)到内存外的一个备份区,以便腾出足够的内存空间,再把已经具备运行条件的进程或进所需的程序或数据,调入内存,随后可以被换回内存继续执行
  • 备份区:是一个固定的足够大的可以容纳所有用户内存映像拷贝的快速磁盘;必须提供对这些内存映像的直接访问
  • 示意图:
  • 特点
    • 滚入,滚出(Roll out, roll in):交换由于基于优先级的算法而不同,低优先级的进程被换出,这样高优先级的进程可以被装入和执行
    • 交换时间的主要部分是转移时间,总的转移时间直接同交换的内存的数量成正比
    • 交换较为耗时(100MB大约4s)
    • 交换时间的主要部分是转移时间,总的转移时间直接同交换的内存数量成正比
    • 标准交换技术在现代操作系统中一般很少使用
      • 常用策略:当空闲内存不够时采用交换(如Unix)

需要考虑的问题

  • 进程的哪些内存要交换到磁盘
    • 运行时创建或修改的内容:栈和堆
  • 在磁盘的什么位置保存被换出的进程?
    • 交换区备份区):系统指定一块特殊的磁盘区域作为交换空间(swap space),包含连续的磁道,操作系统可以使用底层的磁盘读写操作对其高效访问
    • 不同操作系统交换区的叫法不同
      • Windows系统成为页文件pagefile
  • 何时需要发生交换
    • 一种是进程只要不用就换出(很少再用)
    • 第二种使用更多的策略是内存空间不够或有不够的危险时启动交换程序换出(Linux就是采用这种策略)
    • 这个过程与调度器结合起来
  • 如何选择被换出的进程
    • 考虑进程的各种属性:哪些进程不应该换出
    • 如紧缩技术,有些进程在内存里是不能随意搬家的
    • 处于等待I/O状态的进程也不能被随意交换出去
  • 如何处理进程空间增长
    • 一般采用预留空间的方法

posted @ 2020-11-12 21:24  方知有  阅读(1270)  评论(0编辑  收藏  举报