======第四章存储器管理======

Posted dearQiHao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了======第四章存储器管理======相关的知识,希望对你有一定的参考价值。

  存储器是计算机系统的重要组成部分。近年来,存储器容量虽然一直在不断扩大,但是仍不能满足现代软件发展的需要,因此,存储器仍然是一种宝贵而又紧俏的资源。如何对它加以有效的管理,不仅直接影响到存储器的利用率,而且还对系统性能有重大影响。存 储器管理的主要对象是内存。由于对外存的管理与对内存的管理相类似,只是它们的用途 不同,即外存主要用来存放文件,所以我们把对外存的管理放在文件管理一章介绍。

4.1 存储器的层次结构

  在理想情况下存储器的速度应当非常快,能跟上处理机的速度,容量也非常大而且价 格还应很便宜。但目前无法同时满足这样三个条件。于是在现代计算机系统中,存储部件 通常是采用层次结构来组织的。

4.1.1 多级存储器结构

  对于通用计算机而言,存储层次至少应具有三级:最高层为 CPU 寄存器,中间为主存, 最底层是辅存。在较高档的计算机中,还可以根据具体的功能分工细划为寄存器、高速缓存、主存储器、磁盘缓存、固定磁盘、可移动存储介质等 6 层。如图 4-1 所示,在存储层次 中越往上,存储介质的访问速度越快,价格也越高,相对存储容量也越小。其中,寄存器、 高速缓存、主存储器和磁盘缓存均属于操作系统存储管理的管辖范畴,掉电后它们存储的信息不再存在。固定磁盘和可移动存储介质属于设备管理的管辖范畴,它们存储的信息将被长期保存。在这里插入图片描述
  在计算机系统存储层次中,寄存器和主存储器又被称为可执行存储器,存放于其中的信息与存放于辅存中的信息相比较而言,计算机所采用的访问机制是不同的,所需耗费的 时间也是不同的。进程可以在很少的时钟周期内使用一条 load 或 store 指令对可执行存储器进行访问,但对辅存的访问则需要通过 I/O 设备来实现,因此,访问中将涉及到中断、设备驱动程序以及物理设备的运行,所需耗费的时间远远高于对可执行存储器访问的时间,一 般相差 3 个数量级甚至更多。
  对于不同层次的存储介质,由操作系统进行统一管理。操作系统的存储管理,负责对可执行存储器的分配、回收以及提供在存储层次间数据移动的管理机制,例如主存与磁盘 缓存、高速缓存与主存间的数据移动等。在设备和文件管理中,根据用户的需求提供对辅存的管理机制。本章主要讨论有关存储管理部分的问题,对于辅存部分,则放在以后的章节中进行。

4.1.2 主存储器与寄存器

  1. 主存储器

  主存储器(简称内存或主存)是计算机系统中一个主要部件,用于保存进程运行时的程序和数据,也称可执行存储器,其容量对于当前的微机系统和大中型机,可能一般为数十 MB 到数 GB,而且容量还在不断增加,而嵌入式计算机系统一般仅有几十 KB 到几 MB。CPU 的控制部件只能从主存储器中取得指令和数据,数据能够从主存储器读取并将它们装入到寄存器中,或者从寄存器存入到主存储器。CPU 与外围设备交换的信息一般也依托于主存储器地址空间。由于主存储器的访问速度远低于 CPU 执行指令的速度,为缓和这一矛盾, 在计算机系统中引入了寄存器和高速缓存。

  1. 寄存器

  寄存器访问速度最快,完全能与 CPU 协调工作,但价格却十分昂贵,因此容量不可能做得很大。寄存器的长度一般以字(word)为单位。寄存器的数目,对于当前的微机系统和大中型机,可能有几十个甚至上百个;而嵌入式计算机系统一般仅有几个到几十个。寄存器用于加速存储器的访问速度,如用寄存器存放操作数,或用作地址寄存器加快地址转换速 度等。

4.1.3 高速缓存和磁盘缓存

  1. 高速缓存

  高速缓存是现代计算机结构中的一个重要部件,其容量大于或远大于寄存器,而比内 存约小两到三个数量级左右,从几十 KB 到几 MB,访问速度快于主存储器。
  根据程序执行的局部性原理(即程序在执行时将呈现出局部性规律,在一较短的时间内, 程序的执行仅局限于某个部分),将主存中一些经常访问的信息存放在高速缓存中,减少访问主存储器的次数,可大幅度提高程序执行速度。通常,进程的程序和数据是存放在主存储器中,每当使用时,被临时复制到一个速度较快的高速缓存中。当 CPU 访问一组特定信 息时,首先检查它是否在高速缓存中,如果已存在,可直接从中取出使用,以避免访问主存,否则,再从主存中读出信息。如大多数计算机有指令高速缓存,用来暂存下一条欲执 行的指令,如果没有指令高速缓存,CPU 将会空等若干个周期,直到下一条指令从主存中 取出。由于高速缓存的速度越高价格也越贵,故有的计算机系统中设置了两级或多级高速缓存。紧靠内存的一级高速缓存的速度最高,而容量最小,二级高速缓存的容量稍大,速度也稍低。

  1. 磁盘缓存

  由于目前磁盘的 I/O 速度远低于对主存的访问速度,因此将频繁使用的一部分磁盘数据和信息,暂时存放在磁盘缓存中,可减少访问磁盘的次数。磁盘缓存本身并不是一种实际存在的存储介质,它依托于固定磁盘,提供对主存储器存储空间的扩充,即利用主存中的存储空间,来暂存从磁盘中读出(或写入)的信息。主存也可以看做是辅存的高速缓存,因为, 辅存中的数据必须复制到主存方能使用;反之,数据也必须先存在主存中,才能输出到 辅存。
  一个文件的数据可能出现在存储器层次的不同级别中,例如,一个文件数据通常被存 储在辅存中(如硬盘),当其需要运行或被访问时,就必须调入主存,也可以暂时存放在主存的磁盘高速缓存中。大容量的辅存常常使用磁盘,磁盘数据经常备份到磁带或可移动磁盘组上,以防止硬盘故障时丢失数据。有些系统自动地把老文件数据从辅存转储到海量存储器中,如磁带上,这样做还能降低存储价格。

4.2 程序的装入和链接

  在多道程序环境下,要使程序运行,必须先为之创建进程。而创建进程的第一件事, 便是将程序和数据装入内存。如何将一个用户源程序变为一个可在内存中执行的程序,通 常都要经过以下几个步骤:

  首先是要编译,由编译程序将用户源代码编译成若干 个目标模块;
  其次是链接,由链接程序将编译后形成的一组目标模块,以及它们所需要的库函数链接在一起,形成一个完整的装入模块;
  最后是装入,由装入程序将装入模块装入内存。

      图 4-2 示出了这样的三步过程。在这里插入图片描述

4.2.1 程序的装入

  为了阐述上的方便,我们先介绍一个无需进行链接的单个目标模块的装入过程。该目 标模块也就是装入模块。在将一个装入模块装入内存时,可以有绝对装入方式、可重定位 装入方式和动态运行时装入方式,下面分别简述之。

  1. 绝对装入方式

  在编译时,如果知道程序将驻留在内存的什么位置,那么,编译程序将产生绝对地址的目标代码。例如,事先已知用户程序(进程)驻留在从 R 处开始的位置,则编译程序所产生 的目标模块(即装入模块)便从 R 处开始向上扩展。绝对装入程序按照装入模块中的地址,将 程序和数据装入内存。装入模块被装入内存后,由于程序中的逻辑地址与实际内存地址完全相同,故不须对程序和数据的地址进行修改。
  程序中所使用的绝对地址,既可在编译或汇编时给出,也可由程序员直接赋予。但在由程序员直接给出绝对地址时,不仅要求程序员熟悉内存的使用情况,而且一旦程序或数 据被修改后,可能要改变程序中的所有地址。因此,通常是宁可在程序中采用符号地址, 然后在编译或汇编时,再将这些符号地址转换为绝对地址。

  1. 可重定位装入方式

  绝对装入方式只能将目标模块装入到内存中事先指定的位置。在多道程序环境下,编 译程序不可能预知所编译的目标模块应放在内存的何处,因此,绝对装入方式只适用于单道程序环境。在多道程序环境下,所得到的目标模块的起始地址通常是从 0 开始的,程序中的其它地址也都是相对于起始地址计算的。此时应采用可重定位装入方式,根据内存的 当前情况,将装入模块装入到内存的适当位置。
  值得注意的是,在采用可重定位装入程序将装入模块装入内存后,会使装入模块中的所有逻辑地址与实际装入内存的物理地址不同,图 4-3 示出了这一情况。例如,在用户程序的 1000 号单元处有一条指令 LOAD 1,2500,该指令的功能是将 2500 单元中的整数 365 取至寄存器 1。但若将该用户程序装入到内存的 10000~15000 作业地址空间 号单元而不进行地址变换,则在执行 11000 号单元中的指令时,它将仍从 2500 号单元中把数据 内存空间取至寄存器 1 而导致数据错误。由图 4-3 可见, 图 4-3 作业装入内存时的情况 正确的方法应该是将取数指令中的地址 2500 修 改成 12500,即把指令中的相对地址 2500 与本程序在内存中的起始地址 10000 相加,才得 到正确的物理地址 12500。除了数据地址应修改外,指令地址也须做同样的修改,即将指令 的相对地址 1000 与起始地址 10000 相加,得到绝对地址 11000。通常是把在装入时对目标 程序中指令和数据的修改过程称为重定位。又因为地址变换通常是在装入时一次完成的, 以后不再改变,故称为静态重定位。

  1. 动态运行时装入方式

  可重定位装入方式可将装入模块装入到内存中任何允许的位置,故可用于多道程序环 境;但这种方式并不允许程序运行时在内存中移动位置。因为,程序在内存中的移动,意 味着它的物理位置发生了变化,这时必须对程序和数据的地址(是绝对地址)进行修改后方能 运行。然而,实际情况是,在运行过程中它在内存中的位置可能经常要改变,此时就应采用动态运行时装入的方式。
  动态运行时的装入程序在把装入模块装入内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存 后的所有地址都仍是相对地址。为使地址转换不影响指令的执行速度,这种方式需要一个 重定位寄存器的支持,我们将在 4.3 节中做详细介绍。

4.2.2 程序的链接

源程序经过编译后,可得到一组目标模块,再利用链接程序将这组目标模块链接,形 成装入模块。根据链接时间的不同,可把链接分成如下三种:

  (1) 静态链接。在程序运行之前,先将各目标模块及它们所需的库函数,链接成一个完整的装配模块,以后不再拆开。我们把这种事先进行链接的方式称为静态链接方式。

  (2) 装入时动态链接。这是指将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接的链接方式。

  (3) 运行时动态链接。这是指对某些目标模块的链接,是在程序执行中需要该(目标)模块时,才对它进行的链接。

  1. 静态链接方式
      我们通过一个例子来说明在实现静态链接时应解决的一些问题。在图 4-4(a)中示出了经 过编译后所得到的三个目标模块 A、B、C,它们的长度分别为 L、M 和 N。在模块 A 中有一 条语句 CALL B,用于调用模块 B。在模块 B 中有一条语句 CALL C,用于调用模块 C。B 和 C 都属于外部调用符号,在将这几个目标模块装配成一个装入模块时,须解决以下两个问题:

  (1) 对相对地址进行修改。在由编译程序所产生的所有目标模块中,使用的都是相对地址,其起始地址都为 0,每个模块中的地址都是相对于起始地址计算的。在链接成一个装入模块后,原模块 B 和 C 在装入模块的起始地址不再是 0,而分别是 L 和 L+M,所以此时须 修改模块 B 和 C 中的相对地址,即把原 B 中的所有相对地址都加上 L,把原 C 中的所有相对地址都加上 L+M。
  (2) 变换外部调用符号。将每个模块中所用的外部调用符号也都变换为相对地址,如把 B 的起始地址变换为 L,把 C 的起始地址变换为 L+M,如图 4-4(b)所示。这种先进行链接所形成的一个完整的装入模块,又称为可执行文件。通常都不再拆开它,要运行时可直接 将它装入内存。这种事先进行链接,以后不再拆开的链接方式,称为静态链接方式。在这里插入图片描述

  1. 装入时动态链接
      用户源程序经编译后所得的目标模块,是在装入内存时边装入边链接的,即在装入一个目标模块时,若发生一个外部模块调用事件,将引起装入程序去找出相应的外部目标模 块,并将它装入内存,还要按照图 4-4 所示的方式来修改目标模块中的相对地址。装入时动态链接方式有以下优点:

  (1) 便于修改和更新。对于经静态链接装配在一起的装入模块,如果要修改或更新其中的某个目标模块,则要求重新打开装入模块。这不仅是低效的,而且有时是不可能的。若采用动态链接方式,由于各目标模块是分开存放的,所以要修改或更新各目标模块是件非常容易的事。
  (2) 便于实现对目标模块的共享。在采用静态链接方式时,每个应用模块都必须含有其目标模块的拷贝,无法实现对目标模块的共享。但采用装入时动态链接方式,OS 则很容易将一个目标模块链接到几个应用模块上,实现多个应用程序对该模块的共享。

  1. 运行时动态链接

  在许多情况下,应用程序在运行时,每次要运行的模块可能是不相同的。但由于事先无法知道本次要运行哪些模块,故只能是将所有可能要运行到的模块都全部装入内存,并 在装入时全部链接在一起。显然这是低效的,因为往往会有些目标模块根本就不运行。比
  近几年流行起来的运行时动态链接方式,是对上述在装入时链接方式的一种改进。这种链接方式是将对某些模块的链接推迟到程序执行时才进行链接,亦即,在执行过程中, 当发现一个被调用模块尚未装入内存时,立即由 OS 去找到该模块并将之装入内存,把它链接到调用者模块上。凡在执行过程中未被用到的目标模块,都不会被调入内存和被链接到装入模块上,这样不仅可加快程序的装入过程,而且可节省大量的内存空间

4.3 连续分配方式

  连续分配方式可进一步分为单一连续分配、固定分区分配、动态分区分配以及动态重定位分区分配四种方式。

4.3.1 单一连续分配

  这是最简单的一种存储管理方式,但只能用于单用户、单任务的操作系统中。采用这种存储管理方式时,可把内存分为系统区和用户区两部分,系统区仅提供给 OS 使用,通常是放在内存的低址部分;用户区是指除系统区以外的全部内存空间,提供给用户使用。
  虽然在早期的单用户、单任务操作系统中,有不少都配置了存储器保护机构,用于防止用户程序对操作系统的破坏,但近年来常见的几种单用户操作系统中,如 CP/M、MS-DOS 及 RT11 等,都未采取存储器保护措施。这是因为,一方面可以节省硬件,另一方面也因为这是可行的。在单用户环境下,机器由一用户独占,不可能存在其他用户干扰的问题;这时可能出现的破坏行为也只是用户程序自己去破坏操作系统,其后果并不严重,只是会影响该用户程序的运行,且操作系统也很容易通过系统的再启动而重新装入内存。

4.3.2 固定分区分配

  固定分区式分配是最简单的一种可运行多道程序的存储管理方式。这是将内存用户空间划分为若干个固定大小的区域,在每个分区中只装入一道作业,这样,把用户空间划分 为几个分区,便允许有几道作业并发运行。当有一空闲分区时,便可以再从外存的后备作业队列中选择一个适当大小的作业装入该分区,当该作业结束时,又可再从后备作业队列 中找出另一作业调入该分区。

  1. 划分分区的方法

  (1) 分区大小相等,即使所有的内存分区大小相等。其缺点是缺乏灵活性,即当程序太 小时,会造成内存空间的浪费;当程序太大时,一个分区又不足以装入该程序,致使该程序无法运行。尽管如此,这种划分方式仍被用于利用一台计算机去控制多个相同对象的场 合,因为这些对象所需的内存空间是大小相等的。例如,炉温群控系统,就是利用一台计 算机去控制多台相同的冶炼炉。
  (2) 分区大小不等。为了克服分区大小相等而缺乏灵活性的这个缺点,可把内存区划分成含有多个较小的分区、适量的中等分区及少量的大分区。这样,便可根据程序的大小为 之分配适当的分区。

  1. 内存分配

  为了便于内存分配,通常将分区按大小进行排队,并为之建立一张分区使用表,其中各表项包括每个分区的起始地址、大小及状态(是否已分配),见图 4-5(a)所示。当有一用户 程序要装入时,由内存分配程序检索该表,从中找出一个能满足要求的、尚未分配的分区, 将之分配给该程序,然后将该表项中的状态置为“已分配”;若未找到大小足够的分区,则拒绝为该用户程序分配内存。存储空间分配情况如图 4-5(b)所示。在这里插入图片描述
  固定分区分配,是最早的多道程序的存储管理方式。由于每个分区的大小固定,必然会造成存储空间的浪费,因而现在已很少将它用于通用的计算机中;但在某些用于控制多个相同对象的控制系统中,由于每个对象的控制程序大小相同,是事先已编好的,其所需的数据也是一定的,故仍采用固定分区式存储管理方式。

4.3.3. 动态分区分配

  动态分区分配是根据进程的实际需要,动态地为之分配内存空间。在实现可变分区分配时,将涉及到分区分配中所用的数据结构、分区分配算法和分区的分配与回收操作这样三个问题。

  1. 分区分配中的数据结构

  (1) 空闲分区表。在系统中设置一张空闲分区表,用于记录每个空闲分区的情况。每个空闲分区占一个表目,表目中包括分区序号、分区始址及分区的大小等数据项。
  (2) 空闲分区链。为了实现对空闲分区的分配和链接,在每个分区的起始部分,设置一些用于控制分区分配的信息,以及用于链接各分区所用的前向指针;在分区尾部则设置一后向指针,通过前、后向链接指针,可将所有的空闲分区链接成一个双向链,如图 4-6 所示。为了检索方便,在分区尾部重复设置状态位和分区大小表目。当分区被分配出去以后,把状态位由“0”改为“1”,此时,前、后向指针已无意义。
在这里插入图片描述

  1. 分区分配算法

1)首次适应算法(first fit)
  我们以空闲分区链为例来说明采用 FF 算法时的分配情况。FF 算法要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要 求的空闲分区为止;然后再按照作业的大小,从该分区中划出一块内存空间分配给请求者, 余下的空闲分区仍留在空闲链中。若从链首直至链尾都不能找到一个能满足要求的分区, 则此次内存分配失败,返回。该算法倾向于优先利用内存中低址部分的空闲分区,从而保留了高址部分的大空闲区。这给为以后到达的大作业分配大的内存空间创造了条件。其缺点是低址部分不断被划分,会留下许多难以利用的、很小的空闲分区,而每次查找又都是 从低址部分开始,这无疑会增加查找可用空闲分区时的开销。
2)循环首次适应算法(next fit)
  该算法是由首次适应算法演变而成的。在为进程分配内存空间时,不再是每次都从链首开始查找,而是从上次找到的空闲分区的下一个空闲分区开始查找,直至找到一个能满 足要求的空闲分区,从中划出一块与请求大小相等的内存空间分配给作业。为实现该算法,应设置一起始查寻指针,用于指示下一次起始查寻的空闲分区,并采用循环查找方式,即 如果最后一个(链尾)空闲分区的大小仍不能满足要求,则应返回到第一个空闲分区,比较其 大小是否满足要求。找到后,应调整起始查寻指针。该算法能使内存中的空闲分区分布得 更均匀,从而减少了查找空闲分区时的开销,但这样会缺乏大的空闲分区。
3)最佳适应算法(best fit)
  所谓“最佳”是指每次为作业分配内存时,总是把能满足要求、又是最小的空闲分区 分配给作业,避免“大材小用”。为了加速寻找,该算法要求将所有的空闲分区按其容量以从小到大的顺序形成一空闲分区链。这样,第一次找到的能满足要求的空闲区,必然是最佳的。孤立地看,最佳适应算法似乎是最佳的,然而在宏观上却不一定。因为每次分配后所切割下来的剩余部分总是最小的,这样,在存储器中会留下许多难以利用的小空闲区。
4)最坏适应算法(worst fit)
  最坏适应分配算法要扫描整个空闲分区表或链表,总是挑选一个最大的空闲区分割给作业使用,其优点是可使剩下的空闲区不至于太小,产生碎片的几率最小,对中、小作业有利,同时最坏适应分配算法查找效率很高。该算法要求将所有的空闲分区按其容量以从 大到小的顺序形成一空闲分区链,查找时只要看第一个分区能否满足作业要求。但是该算法的缺点也是明显的,它会使存储器中缺乏大的空闲分区。最坏适应算法与前面所述的首次适应算法、循环首次适应算法、最佳适应算法一起,也称为顺序搜索法
5)快速适应算法(quick fit)
  该算法又称为分类搜索法,是将空闲分区根据其容量大小进行分类,对于每一类具有相同容量的所有空闲分区,单独设立一个空闲分区链表,这样,系统中存在多个空闲分区链表,同时在内存中设立一张管理索引表,该表的每一个表项对应了一种空闲分区类型, 并记录了该类型空闲分区链表表头的指针。空闲分区的分类是根据进程常用的空间大小进 行划分,如 2 KB、4 KB、8 KB 等,对于其它大小的分区,如 7 KB 这样的空闲区,既可以 放在 8 KB 的链表中,也可以放在一个特殊的空闲区链表中。
  该算法的优点是查找效率高,仅需要根据进程的长度,寻找到能容纳它的最小空闲区链表,并取下第一块进行分配即可。另外该算法在进行空闲分区分配时,不会对任何分区产生分割,所以能保留大的分区,满足对大空间的需求,也不会产生内存碎片。
  该算法的缺点是在分区归还主存时算法复杂,系统开销较大。此外,该算法在分配空闲分区时是以进程为单位,一个分区只属于一个进程,因此在为进程所分配的一个分区中, 或多或少地存在一定的浪费。空闲分区划分越细,浪费则越严重,整体上会造成可观的存 储空间浪费,这是典型的以空间换时间的作法。

  1. 分区分配操作
      在动态分区存储管理方式中,主要的操作是分配内存和回收内存。

  1)分配内存
  系统应利用某种分配算法,从空闲分区链(表)中找到所需大小的分区。设请求的分区大小为 u.size,表中每个空闲分区的大小可表示为 m.size。若 m.size-u.size≤size(size 是事先规定的不再切割的剩余分区的大小),说明多余部分太小,可不再切割,将整个分区分配给请求者;否则(即多余部分超过 size),从该分区中按请求的大小划分出一块内存空间分配出去,余下的部分仍留在空闲分区链(表)中。然后,将分配区的首址返回给调用者。图 4-7 示出了 分配流程。在这里插入图片描述
  2) 回收内存
  当进程运行完毕释放内存时,系统根据回收区的首址,从空闲区链(表)中找到相应的插 入点,此时可能出现以下四种情况之一:
  (1) 回收区与插入点的前一个空闲分区 F 1 相邻接,见图 4-8(a)。此时应将回收区与插入点的前一分区合并,不必为回收分区分配新表项,而只需修改其前一分区 F 1 的大小。
  (2) 回收分区与插入点的后一空闲分区 F 2 相邻接,见图 4-8(b)。此时也可将两分区合并, 形成新的空闲分区,但用回收区的首址作为新空闲区的首址,大小为两者之和。
  (3) 回收区同时与插入点的前、后两个分区邻接,见图 4-8©。此时将三个分区合并, 使用 F 1 的表项和 F 1 的首址,取消 F 2 的表项,大小为三者之和。
  (4) 回收区既不与 F 1 邻接,又不与 F 2 邻接。这时应为回收区单独建立一新表项,填写回收区的首址和大小,并根据其首址插入到空闲链中的适当位置。在这里插入图片描述

4.3.4 伙伴系统

  固定分区和动态分区方式都有不足之处。固定分区方式限制了活动进程的数目,当进 程大小与空闲分区大小不匹配时,内存空间利用率很低。动态分区方式算法复杂,回收空 闲分区时需要进行分区合并等,系统开销较大。伙伴系统方式是对以上两种内存方式的一 种折衷方案。
  伙伴系统规定,无论已分配分区或空闲分区,其大小均为 2 的 k 次幂,k 为整数, l≤k≤m,其中:2 1 表示分配的最小分区的大小,2 m 表示分配的最大分区的大小,通常 2m 是整个可分配内存的大小。
  假设系统的可利用空间容量为 2 m 个字,则系统开始运行时,整个内存区是一个大小为 2m 的空闲分区。在系统运行过程中,由于不断的划分,可能会形成若干个不连续的空闲分区, 将这些空闲分区根据分区的大小进行分类,对于每一类具有相同大小的所有空闲分区,单独 设立一个空闲分区双向链表。这样,不同大小的空闲分区形成了 k(0≤k≤m)个空闲分区链表。
  当需要为进程分配一个长度为 n 的存储空间时,首先计算一个 i 值,使 2i - 1 <n≤2i ,然 后在空闲分区大小为 2 i 的空闲分区链表中查找。若找到,即把该空闲分区分配给进程。否 则,表明长度为 2 i 的空闲分区已经耗尽,则在分区大小为 2 i + 1 的空闲分区链表中寻找。若 存在 2 i + 1 的一个空闲分区,则把该空闲分区分为相等的两个分区,这两个分区称为一对伙 伴,其中的一个分区用于分配,而把另一个加入分区大小为 2 i 的空闲分区链表中。若大小 为 2 i + 1 的空闲分区也不存在,则需要查找大小为 2 i + 2 的空闲分区,若找到则对其进行两次分 割:第一次,将其分割为大小为 2 i + 1 的两个分区,一个用于分配,一个加入到大小为 2i + 1 的空闲分区链表中;第二次,将第一次用于分配的空闲区分割为 2 i 的两个分区,一个用于 分配,一个加入到大小为 2 i 的空闲分区链表中。若仍然找不到,则继续查找大小为 2 i + 3 的 空闲分区,以此类推。由此可见,在最坏的情况下,可能需要对 2 k 的空闲分区进行 k 次分 割才能得到所需分区。
  与一次分配可能要进行多次分割一样,一次回收也可能要进行多次合并,如回收大小 为 2 i 的空闲分区时,若事先已存在 2 i 的空闲分区时,则应将其与伙伴分区合并为大小为 2 i + 1 的空闲分区,若事先已存在 2 i + 1 的空闲分区时,又应继续与其伙伴分区合并为大小为 2 i + 2 的空闲分区,依此类推。
  在伙伴系统中,其分配和回收的时间性能取决于查找空闲分区的位置和分割、合并空 闲分区所花费的时间。与前面所述的多种方法相比较,由于该算法在回收空闲分区时,需 要对空闲分区进行合并,所以其时间性能比前面所述的分类搜索算法差,但比顺序搜索算 法好,而其空间性能则远优于前面所述的分类搜索法,比顺序搜索法略差。
  需要指出的是,在当前的操作系统中,普遍采用的是下面将要讲述的基于分页和分段 机制的虚拟内存机制,该机制较伙伴算法更为合理和高效,但在多处理机系统中,伙伴系 统仍不失为一种有效的内存分配和释放的方法,得到了大量的应用。

4.3.5 哈希算法

  哈希算法就是利用哈希快速查找的优点,以及空闲分区在可利用空间表中的分布规律, 建立哈希函数,构造一张以空闲分区大小为关键字的哈希表,该表的每一个表项记录了一 个对应的空闲分区链表表头指针。
  当进行空闲分区分配时,根据所需空闲分区大小,通过哈希函数计算,即得到在哈希表中的位置,从中得到相应的空闲分区链表,实现最佳分配策略。

4.3.6. 可重定位分区分配

  1. 动态重定位的引入

  在连续分配方式中,必须把一个系统或用户程序装入一连续的内存空间。如果在系统中只有若干个小的分区,即使它们容量的总和大于要装入的程序,但由于这些分区不相邻 接,也无法把该程序装入内存。例如,图 4-9(a)中示出了在内存中现有四个互不邻接的小分 区,它们的容量分别为 10 KB、30 KB、14 KB 和 26 KB,其总容量是 80 KB。但如果现在有一作业到达,要求获得 40 KB 的内存空间,由于必须为它分配一连续空间,故此作业无法装入。这种不能被利用的小分区称为“零头”或“碎片”。
在这里插入图片描述
  若想把作业装入,可采用的一种方法是:将内存中的所有作业进行移动,使它们全都相邻接,这样,即可把原来分散的多个小分区拼接成一个大分区,这时就可把作业装入该 区。这种通过移动内存中作业的位置,以把原来多个分散的小分区拼接成一个大分区的方 法,称为“拼接”或“紧凑”,见图 4-9(b)。由于经过紧凑后的某些用户程序在内存中的位置发生了变化,此时若不对程序和数据的地址加以修改(变换),则程序必将无法执行。为此, 在每次“紧凑”后,都必须对移动了的程序或数据进行重定位

  1. 动态重定位的实现

  在动态运行时装入的方式中,作业装入内存后的所有地址都仍然是相对地址,将相对地址转换为物理地址的工作,被推迟到程序指令要真正执行时进行。为使地址的转换不会影响到指令的执行速度,必须有硬件地址变换机构的支持,即须在系统中增设一个重定位寄存器,用它来存放程序(数据)在内存中的起始地址。程序在执行时,真正访问的内存地址是相对地址与重定位寄存器中的地址相加而形成的。图 4-10 示出了动态重定位的实现原理。 地址变换过程是在程序执行期间,随着对每条指令或数据的访问自动进行的,故称为动态重定位。当系统对内存进行了“紧凑”而使若干程序从内存的某处移至另一处时,不需对程序做任何修改,只要用该程序在内存的新起始地址,去置换原来的起始地址即可。在这里插入图片描述

  1. 动态重定位分区分配算法
      动态重定位分区分配算法与动态分区分配算法基本上相同,差别仅在于:在这种分配算法中,增加了紧凑的功能,通常,在找不到足够大的空闲分区来满足用户需求时进行紧 凑。图 4-11 示出了动态重定位分区分配算法。在这里插入图片描述

4.3.7 对换

  1. 对换的引入

  在多道程序环境下,一方面,在内存中的某些进程由于某事件尚未发生而被阻塞运行, 但它却占用了大量的内存空间,甚至有时可能出现在内存中所有进程都被阻塞而迫使 CPU 停止下来等待的情况;另一方面,却又有着许多作业在外存上等待,因无内存而不能进入 内存运行的情况。显然这对系统资源是一种严重的浪费,且使系统吞吐量下降。为了解决 这一问题,在系统中又增设了对换(也称交换)设施。所谓“对换”,是指把内存中暂时不能运行的进程或者暂时不用的程序和数据调出到外存上,以便腾出足够的内存空间,再把已具备运行条件的进程或进程所需要的程序和数据调入内存。对换是提高内存利用率的有效措施。自从在 60 年代初期出现“对换”技术后,它便引起了人们的重视,现在该技术已被 广泛地应用于操作系统中。
  如果对换是以整个进程为单位的,便称之为“整体对换”或“进程对换”。这种对换被 广泛地应用于分时系统中,其目的是用来解决内存紧张问题,并可进一步提高内存的利用率。而如果对换是以“页”或“段”为单位进行的,则分别称之为“页面对换”或“分段对换”,又统称为“部分对换”。这种对换方法是实现后面要讲到的请求分页和请求分段式存储管理的基础,其目的是为了支持虚拟存储系统。为了实现进程对换,系统必须能实现三方面的功能: 对换空间的管理、进程的换出,以及进程的换入。

  1. 对换空间的管理

  在具有对换功能的 OS 中,通常把外存分为文件区和对换区。前者用于存放文件,后者 用于存放从内存换出的进程。由于通常的文件都是较长久地驻留在外存上,故对文件区管理的主要目标,是提高文件存储空间的利用率,为此,对文件区采取离散分配方式。然而,进 程在对换区中驻留的时间是短暂的,对换操作又较频繁,故对对换空间管理的主要目标, 是提高进程换入和换出的速度。 为此,采取的是连续分配方式,较少考虑外存中的碎片 问题。
  为了能对对换区中的空闲盘块进行管理,在系统中应配置相应的数据结构,以记录外存的使用情况。其形式与内存在动态分区分配方式中所用数据结构相似,即同样可以用空 闲分区表或空闲分区链。在空闲分区表中的每个表目中应包含两项,即对换区的首址及其大小,分别用盘块号和盘块数表示。
  由于对换分区的分配是采用连续分配方式,因而对换空间的分配与回收,与动态分区方式时的内存分配与回收方法雷同。其分配算法可以是首次适应算法、循环首次适应算法 或最佳适应算法。具体的分配操作,也与图 4-7 中内存的分配过程相同,这里不再赘述。

  1. 进程的换入与换出

  (1) 进程的换出。每当一进程由于创建子进程而需要更多的内存空间,但又无足够的内存空间等情况发生时,系统应将某进程换出。其过程是:系统首先选择处于阻塞状态且优先级最低的进程作为换出进程,然后启动磁盘,将该进程的程序和数据传送到磁盘的对换区上。若传送过程未出现错误,便可回收该进程所占用的内存空间,并对该进程的进程控制块做相应的修改。
  (2) 进程的换入。系统应定时地查看所有进程的状态,从中找出“就绪”状态但已换出 的进程,将其中换出时间最久(换出到磁盘上)的进程作为换入进程,将之换入,直至已无可 换入的进程或无可换出的进程为止。

4.4 基本分页存储管理方式

  连续分配方式会形成许多“碎片”,虽然可通过“紧凑”方法将许多碎片拼接成可用的 大块空间,但须为之付出很大开销。如果允许将一个进程直接分散地装入到许多不相邻接 的分区中,则无须再进行“紧凑”。基于这一思想而产生了离散分配方式。如果离散分配的基本单位是页,则称为分页存储管理方式;如果离散分配的基本单位是段,则称为分段存储管理方式。

  在分页存储管理方式中,如果不具备页面对换功能,则称为基本的分页存储管理方式, 或称为纯分页存储管理方式,它不具有支持实现虚拟存储器的功能,它要求把每个作业全部装入内存后方能运行。

4.4.1页面与页表

  1. 页面

  1) 页面和物理块
  分页存储管理是将一个进程的逻辑地址空间分成若干个大小相等的片,称为页面或页, 并为各页加以编号,从 0 开始,如第 0 页、第 1 页等。相应地,也把内存空间分成与页面相同大小的若干个存储块,称为(物理)块或页框(frame),也同样为它们加以编号,如 0# 块、 1# 块等等。在为进程分配内存时,以块为单位将进程中的若干个页分别装入到多个可以不相邻接的物理块中。由于进程的最后一页经常装不满一块而形成了不可利用的碎片,称之为 “页内碎片”。
  2) 页面大小
  在分页系统中的页面其大小应适中。页面若太小,一方面虽然可使内存碎片减小,从而减少了内存碎片的总空间,有利于提高内存利用率,但另一方面也会使每个进程占用较多的页面,从而导致进程的页表过长,占用大量内存;此外,还会降低页面换进换出的效 率。然而,如果选择的页面较大,虽然可以减少页表的长度,提高页面换进换出的速度, 但却又会使页内碎片增大。因此,页面的大小应选择适中,且页面大小应是 2 的幂,通常 为 512 B~8 KB

  1. 地址结构

  分页地址中的地址结构如下:在这里插入图片描述
  它含有两部分:前一部分为页号 P,后一部分为位移量 W(或称为页内地址)。图中的地址长度为 32 位,其中 0~11 位为页内地址,即每页的大小为 4 KB;12~31 位为页号,地址空间最多允许有 1 M 页。
  对于某特定机器,其地址结构是一定的。若给定一个逻辑地址空间中的地址为 A,页 面的大小为 L,则页号 P 和页内地址 d 可按下式求得:在这里插入图片描述
其中,INT 是整除函数,MOD 是取余函数。例如,其系统的页面大小为 1 KB,设 A = 2170 B, 则由上式可以求得 P = 2,d = 122。

  1. 页表

  在分页系统中,允许将进程的各个页离散地存储在内存不同的物理块中,但系统应能保证进程的正确运行,即能在内存中找到每个页面所对应的物理块。为此,系统又为每个 进程建立了一张页面映像表,简称页表。在进程地址空间内的所有页(0~n),依次在页表中有一页表项,其中记录了相应页在内存中对应的物理块号,见图 4-12 的中间部分。在配置了页表后,进程执行时,通过查找该表,即可找到每页在内存中的物理块号。可见,页表的作用是实现从页号到物理块号的地址映射在这里插入图片描述
  即使在简单的分页系统中,也常在页表的表项中设置一存取控制字段,用于对该存储 块中的内容加以保护。当存取控制字段仅有一位时,可用来规定该存储块中的内容是允许 读/写,还是只读;若存取控制字段为二位,则可规定为读/写、只读和只执行等存取方式。 如果有一进程试图去写一个只允许读的存储块时,将引起操作系统的一次中断。如果要利用分页系统去实现虚拟存储器,则还须增设一数据项。我们将在本章后面做详细介绍。

4.4.2 地址变换机构

  为了能将用户地址空间中的逻辑地址变换为内存空间中的物理地址,在系统中必须设 置地址变换机构。该机构的基本任务是实现从逻辑地址到物理地址的转换。由于页内地址和物理地址是一一对应的(例如,对于页面大小是 1 KB 的页内地址是 0~1023,其相应的 物理块内的地址也是 0~1023,无须再进行转换),因此,地址变换机构的任务实际上只是将逻辑地址中的页号,转换为内存中的物理块号。又因为页面映射表的作用就是用于实现从页号到物理块号的变换,因此,地址变换任务是借助于页表来完成的

  1. 基本地址变换机构

  页表的功能可以由一组专门的寄存器来实现。一个页表项用一个寄存器。由于寄存器具有较高的访问速度,因而有利于提高地址变换的速度;但由于寄存器成本较高,且大多 数现代计算机的页表又可能很大,使页表项的总数可达几千甚至几十万个,显然这些页表项不可能都用寄存器来实现,因此,页表大多驻留在内存中。在系统中只设置一个页表寄存器 PTR,在其中存放页表在内存的始址和页表的长度。平时,进程未执行时,页表的始址和页表长度存放在本进程的 PCB 中。当调度程序调度到某进程时,才将这两个数据装入页表寄存器中。因此,在单处理机环境下,虽然系统中可以运行个进程,但只需一个页表寄存器
  当进程要访问某个逻辑地址中的数据时,分页地址变换机构会自动地将有效地址(相对地址)分为页号和页内地址两部分,再以页号为索引去检索页表。查找操作由硬件执行。在执行检索之前,先将页号与页表长度进行比较,如果页号大于或等于页表长度,则表示本次所访问的地址已超越进程的地址空间。于是,这一错误将被系统发现并产生一地址越界 中断。若未出现越界错误,则将页表始址与页号和页表项长度的乘积相加,便得到该表项在页表中的位置,于是可从中得到该页的物理块号,将之装入物理地址寄存器中。与此同时,再将有效地址寄存器中的页内地址送入物理地址寄存器的块内地址字段中。这样便完 成了从逻辑地址到物理地址的变换。图 4-13 示出了分页系统的地址变换机构。在这里插入图片描述

  1. 具有快表的地址变换机构

  由于页表是存放在内存中的,这使 CPU 在每存取一个数据时,都要两次访问内存。第一次是访问内存中的页表,从中找到指定页的物理块号,再将块号与页内偏移量 W 拼接, 以形成物理地址第二次访问内存时,才是从第一次所得地址中获得所需数据(或向此地址中写入数据)。因此,采用这种方式将使计算机的处理速度降低近 1/2。可见,以此高昂代价 来换取存储器空间利用率的提高,是得不偿失的。

  为了提高地址变换速度,可在地址变换机构中增设一个具有并行查寻能力的特殊高速缓冲寄存器,又称为“联想寄存器”,或称为“快表”,在 IBM 系统中 又取名为 TLB,用以存放当前访问的那些页表项。此时的地址变换过程是:在 CPU 给出有效地址后,由地址变换机构自动地将页号 P 送入高速缓冲寄存器,并将此页号与高速缓存中的所有页号进行比较,若其中有与此相匹配的页号,便表示所要访问的页表项在快表中。于是,可直接从快表中读出该页所对应的物理块号,并送到物理地址寄存器中。如在快表中未找到对应的页表项,则还须再访问内存中的页表,找到后,把从页表项中读出的物理块号送地址寄存器;同时,再将此页表项存入快表的一个寄存器单元中,亦即,重新修改快表。但如果联想寄存器已满,则 OS 必须找到一个老的且已被认为不再需要的页表项,将它换出。图 4-14 示出了具有快表的地址变换机构。在这里插入图片描述
  由于成本的关系,快表不可能做得很大,通常只存放 16~512 个页表项,这对中、小 型作业来说,已有可能把全部页表项放在快表中,但对于大型作业,则只能将其一部分页表项放入其中。由于对程序和数据的访问往往带有局限性,因此,据统计,从快表中能找到所需页表项的机率可达 90%以上。这样,由于增加了地址变换机构而造成的速度损失, 可减少到 10%以下,达到了可接受的程度。

4.4.3 两级和多级页表

  现代的大多数计算机系统,都支持非常大的逻辑地址空间(232 ~264 )。在这样的环境下, 页表就变得非常大,要占用相当大的内存空间。我们可以采用下述两个方法来解决这一问题:

  (1) 采用离散分配方式来解决难以找到一块连续的大内存空间的问题;

  (2) 只将当前需要的部分页表项调入内存,其余的页表项仍驻留在磁盘上,需要时再调入。

  1. 两级页表

  对于要求连续的内存空间来存放页表的问题,可利用将页表进行分页,并离散地将各个页面分别存放在不同的物理块中的办法来加以解决,同样也要为离散分配的页表再建立 一张页表,称为外层页表(Outer Page Table),在每个页表项中记录了页表页面的物理块号。 下面我们仍以前面的 32 位逻辑地址空间为例来说明。当页面大小为 4 KB 时(12 位),若采用一级页表结构,应具有 20 位的页号,即页表项应有 1 兆个;在采用两级页表结构时,再 对页表进行分页,使每页中包含 2 10 (即 1024)个页表项,最多允许有 2 10 个页表分页;或者说,外层页表中的外层页内地址 P 2 为 10 位,外层页号 P 1 也为 10 位。此时的逻辑地址结构 可描述如下:在这里插入图片描述>  由图可以看出,在页表的每个表项中存放的是进程的某页在内存中的物理块号,如第 0# 页存放在 1# 物理块中;1# 页存放在 4# 物理块中。而在外层页表的每个页表项中,所存放的 是某页表分页的首址,如第 0# 页表是存放在第 1011# 物理块中。我们可以利用外层页表和页表这两级页表,来实现从进程的逻辑地址到内存中物理地址间的变换。在这里插入图片描述  为了地址变换实现上的方便起见,在地址变换机构中同样需要增设一个外层页表寄存器,用于存放外层页表的始址,并利用逻辑地址中的外层页号,作为外层页表的索引,从 中找到指定页表分页的始址,再利用 P 2 作为指定页表分页的索引,找到指定的页表项,其中即含有该页在内存的物理块号,用该块号和页内地址 d 即可构成访问的内存物理地址。 图 4-16 示出了两级页表时的地址变换机构。在这里插入图片描述
  上述对页表施行离散分配的方法,虽然解决了对大页表无需大片存储空间的问题,但并未解决用较少的内存空间去存放大页表的问题。换言之,只用离散分配空间的办法并未减少页表所占用的内存空间。解决方法是把当前需要的一批页表项调入内存,以后再根据需要陆续调入。在采用两级页表结构的情况下,对于正在运行的进程,必须将其外层页表调入内存,而对页表则只需调入一页或几页。为了表征某页的页表是否已经调入内存,还应在外层页表项中增设一个状态位 S,其值若为 0,表示该页表分页尚未调入内存;否则, 说明其分页已在内存中。进程运行时,地址变换机构根据逻辑地址中的 P1 ,去查找外层页表;若所找到的页表项中的状态位为 0,则产生一中断信号,请求 OS 将该页表分页调入内存。

  1. 多级页表

  对于 32 位的机器,采用两级页表结构是合适的;但对于 64 位的机器,采用两级页表是否仍可适用的问题,须做以下简单分析。如果页面大小仍采用 4 KB 即 212 B,那么还剩 下 52 位,假定仍按物理块的大小(2 12 位)来划分页表,则将余下的 42 位用于外层页号。此 时在外层页表中可能有 4096 G 个页表项,要占用 16 384 GB 的连续内存空间。这样的结果 显然是不能令人接受的,因此必须采用多级页表,将外层页表再进行分页,也就是将各分页离散地装入到不相邻接的物理块中,再利用第 2 级的外层页表来映射它们之间的关系。
  对于 64 位的计算机,如果要求它能支持 264 B(= 1 844 744 TB)规模的物理存储空间,则 即使是采用三级页表结构也是难以办到的;而在当前的实际应用中也无此必要。故在近两年推出的 64 位 OS 中,把可直接寻址的存储器空间减少为 45 位长度(即 245 )左右,这样便可利用三级页表结构来实现分页存储管理。

4.5 基本分段存储管理方式

  如果说推动存储管理方式从固定分区到动态分区分配,进而又发展到分页存储管理方式的主要动力,是提高内存利用率,那么,引入分段存储管理方式的目的,则主要是为了 满足用户(程序员)在编程和使用

以上是关于======第四章存储器管理======的主要内容,如果未能解决你的问题,请参考以下文章

操作系统学习笔记 第四章:存储器管理(王道考研)

操作系统学习笔记 第四章:存储器管理(王道考研)

第四部分:存储管理

-存储器管理

-存储器管理

-存储器管理