7、操作系统原理知识复习(day07)--内存管理(上)

582 阅读29分钟

第七章:内存管理(上)

1、内存的基础知识

1.1、知识总览

image.png

1.2、什么是内存?有何作用?

内存:内存是用于存放数据的硬件程序执行前需要存放到内存中才能被CPU处理。

思考:在多道程序环境下,系统中会有多个程序并发执行,也就是说会有多个程序的数据需要同时放到内存中。那么,如何区分 各个程序的数据是放在什么地方的呢?

image.png

答:如上图所示:给内存的每个存储单元编地址:内存地址从0开始,每个地址对应一个存储单元其中每个存储单元的大小可以按照字节编址或者按字编址的方式进行区分

  • 按字节编址: 如果计算机“按字节编址”,则每个存储单元大小为1字节,即1B,即8个二进制位
  • 按字编址: 如果字长为16位的计算机"按字编址",则每个存储单元大小为1个字;每个字的大小为16个二进制位

1.3、进程的运行原理--指令

image.png

可见,我们写的代码会被翻译成CPU能识别的机器指令。这些指令会告诉CPU应该去内存的哪个地址存/取数据,这个数据应该做什么样的处理。在这个例子中,指令中直接给出了变量x的实际存放地址 (物理地址),但实际在生成机器指令的时候并不知道该进程所需要的数据具体会被放到什么位置,所以编译生成的指令中一般是使用逻辑地址(相对地址),然后再通过地址映射找到实际的物理地址

1.4、逻辑地址VS物理地址

举例来说,宿舍四个人一起出去旅行,四个人的学号尾号分别是0、1、2、3。住酒店时酒店给你们安排了4个房号相连的房间。四个人按学号递增次序入住房间。比如0、1、2、3号同学分别入住了5、6、7、8号房间。四个人的编号0、1、2、3其实是一个“相对位置”,而各自入住的房间号是一个“绝对位置”。只要知道0号同学住的是房号为N的房间,那么M号同学的房号一定是N+M。也就是说,只要知道各个同学的“相对位置”和“起始房号”,就一定可以算出所有同学的“绝对位置”

指令中的地址也可以采用这种思想。编译时产生的指令只关心“相对地址”,实际放入内存中时再想办法根据起始位置得到“绝对地址”

Eg:编译时只需确定变量x存放的相对地址是100(也就是说相对于进程在内存中的起始地址而言的地址)。CPU想要找到x在内存中的实 际存放位置,只需要用进程的起始地址+100即可。

相对地址又称逻辑地址,绝对地址又称物理地址。

1.5、从写程序到程序运行

image.png

  • 编译: 由编译程序将用户源代码编译成若干个目标模块(编译就是把高级语言翻译为机器语言)
  • 链接: 由链接程序将编译后形成的一组目标模块,以及所需库函数链接在一起,形成一个完整的装入模块
  • 装入(装载): 由装入程序将装入模块装入内存运行,形成最终的物理地址

1.6、地址映射

处理机在执行指令时,必须使用物理地址才能从主在存取信息,而应用程序使用的地址是逻辑辑地址(包括指令地址和操作数地址),这就涉及逻辑地址与物理地址转换的问题。

多用户共享主存时,由系统分配主存。一般情况下,一个应用程序分配到的存储空间和它的逻辑地址空间是不一致的。因此,程序的相应进程在处理机上运行时,所要访问的指令和数据的实际地址和地址空间中的地址是不同的。

为了保证[CPU]执行指令时可正确访问[存储单元],需将[用户程序]中的逻辑[地址转换]为运行时由机器[直接寻址]的物理地址,这一过程称为地址变换,也叫地址映射。存储管理的功能之就是实现这种地址变换

1.7、地址映射的三种方式

实现地址变换的关键是要建立虚——实地址间的对应关系。 而这种对应关系的建立随着应用程序开发的阶段不同而不同。下面,根据应用程序开发的四个不同阶段(编辑、编译、连接、运行)讨论地址映射的方式,即

装入的三种方式:用三种不同的方法完成装入过程中从逻辑地址到物理地址的转换:

绝对装入

  • 即在编程或编译时确定地址映射关系,在编译时,就必须知道程序将放到内存中的哪个位置,因此申请内存时必须具体地提出申请的内存容量和内存的起始地址,而编译程序也将产生绝对地址的目标代码。然后装入程序按照装入模块中的地址,将程序和数据装入内存。

  • 绝对装入只适用于单道程序环境。

  • 程序中使用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。通常情况下都是编译或汇编时再转换为绝对地址。

静态重定位

  • 即静态地址映射,如果虚——实地址间的对应关系是在将程序装入主存时实现的,那么编译和连接的结果就是一个可以浮动的程序模块。当将这一地址空间装入到主存中的任一位置时,若由主存装入程序对有关地址部分进行调整,则这次确定下来的地址就不再改变。

  • 这种在程序装入过程中随即进行的地址变换方式(即地址变换是在装入时一次完成的)。称为静态地址映射或静态重定位

  • 编译、链接后的装入模块的地址都是从0开始的指令中使用的地址、数据存放的地址都是相对于起始地址而言的逻辑地址。可根据内存的当前情况,将装入模块装入到内存的适当位置。装入时对程序地址进行 “重定位”,将逻辑地址变换为物理地址

  • 静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间

image.png

动态重定位

  • 又称动态运行时装入。编译、链接后的装入模块的地址都是从0开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行

  • 因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持(重定位寄存器:存放装入模块存放的起始位置)。

  • 采用动态重定位时允许程序在内存中发生移动

image.png

当某个进程取得CPU控制权时,操作系统负责将该程序在主存的起始地址送入重定位寄存器中,之后在进程的整个运行过程中,每次访问存储器时,重定位寄存器的内容将被自动地加到逻辑地址中去,经这样变换后,执行的结果是正确的

动态地址变换方式比静态重定位要好。因为静态重定位是用软件办法实现的,需要花费较的CPU时间,动态重定位则是由硬件自动完成的,而且重定位寄存器的内容可由操作系统用特权指令来设置, 比较灵活。概括地说,动态重定位能满足以下目标:

  • 具有给用户程序任意分配一个主存区域的能力;
  • 在改变系统设备时,具有不需要重新编程和重新编辑的能力:
  • 具有在任何时刻,在主存可用空间中重新分配一个程序的能力:
  • 对于一个用户程序,具有以间断方式分配主存的能力:
  • 为了能更多地容纳用户程序,具有只装入用户程序的部分代码即可投入运行的能力。

实现动态地址变换需要有一个硬件的地址变换机构,在使用硬件产生物理地址时会有一定的时钟周期廷迟,不过这一延迟是极短的,可以忽略。

1.8、链接的三种方式

  1. 静态链接: 在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的可执行文件(装入模块),之后不再拆开。直接放入内存开始运行【黄色部分】

  2. 装入时动态链接: 将各目标模块装入内存时,边装入边链接的链接方式。

  3. 运行时动态链接: 在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。

image.png

2、内存管理相关功能及概念介绍

2.1、知识总览

image.png

2.2、内存空间的分配与回收

  • 操作系统作为系统资源的管理者,当然也需要对内存进行管理,要管些什么呢?
  • 操作系统要怎么记录哪些内存区域已经被分配出去了,哪些又还空闲?
  • 当进程运行结束之后,如何将进程占用的内存空间回收? 【淘汰策略】
  • 对于新来的进程,内存很多空闲存储区域都可以放,那应该放在哪里? 怎么放?【放置策略】
  • 决定程序装入主存的时机,是在需要信息时调入信息,还是预先调入?这是两种不同的调入策略,前者为请调策略,后者为预调策略。

因此内存管理的第一个功能就是让操作系统负责的内存空间的分配与回收

下面提前了解内存分配相关知识,后续会讲到

在内存分配中,一般对主存区域的划分有两种方式:

  1. 将内存划分成大小不等的区域

  2. 将主存等分为一系列大小相等的

按区分配或段式分配采用第一种划分方式。这种方式使一个内存区域可以存放一个应用程序的连续的地址空间(按区分配),或存放一个应用程序的各个逻辑分段的地址空间(段式系统)。而页式系统往往采用第二种划分方式。这种方式将一个应用程序的地址空间划分成一系列页面, 然后放置到主存的块中去。

调入策略对页式系统或非页式系统没有多大区别,而淘汰策略和放置策略在页式和非页式系统中是不同的。其差别主要在于页式系统中页的大小固定,而非页式系统处理的信息块大小是可变的。

  1. 为了进行主存分配,必须建立相应的数据结构。用于主存分配的数据结构有主存资源信息块(m_rib)、空闲区队列(或存储分块表)。

  2. 对于每一次分配,其分配信息必须保留到相应的数据结构中。如果系统提供虚拟存储能力,则对于虚存的分配必须和辅存的管理相结合。

  3. 内存分配问题直接关系到内存扩充和逻辑地址到物理地址的映射问题,内存管理的这几个功能是不可分割的。

2.3、内存空间的扩展(虚拟存储器)

举例: 游戏GTA的大小超过60GB,按理来说这个游戏程序运行之前需要把60GB数据全部放入内存。然而,实际我的电脑内存才4GB,但为什么这个游戏可以顺利运行呢?--虚拟技术(操作系统的虚拟性)

因此操作系统需要提供某种技术从逻辑上对内存空间进行扩充,具体内容参看下面第3小节

2.4、地址转换(地址映射)

为了使编程更方便,程序员写程序时应该只需要关注指令、数据的逻辑地址。而逻辑地址到物理地址的转换(这个过程称为地址重定位)应该由操作系统负责,这样就保证了程序员写程序时不需要关注物理内存的实际情况。

因此操作系统需要提供地址转换功能,负责程序的逻辑地址与物理地址的转换【三种装入方式实现】

image.png

2.5、内存保护

操作系统需要提供内存保护功能。保证各进程在各自存储空间内运行,互不干扰

image.png

内存保护可采取两种方法:

方法一: 在CPU中设置一对上、下限寄存器,存放进程的上、下限地址。某个进程的指令要访问内存某个地址时,CPU根据上下限寄存器的地址检查其是否越界。

image.png

方法二: 采用重定位寄存器(又称基址寄存器)和界地址寄存器(又称限长寄存器)进行越界检查。重定位寄存器中存放的是进程的起始物理地址,界地址寄存器中存放的是进程的最大逻辑地址

image.png

3、内存空间的扩充--内存覆盖与交换技术

image.png

3.1、覆盖技术

早期的计算机内存很小,比如IBM推出的第一台PC机最大只支持1MB大小的内存。因此经常会出现内存大小不够的情况。后来人们引入了覆盖技术,用来解决“程序大小超过物理内存总和”的问题

覆盖技术的思想:

  • 将程序分为多个段(多个模块)。常用的段常驻内存,不常用的段在需要时调入内存
  • 内存中分为一个“固定区”和若干个“覆盖区”
  • 需要常驻内存的段放在“固定区”中,调入后就不再调出(除非运行结束)
  • 不常用的段放在“覆盖区“,需要用到时调入内存,用不到时调出内存

image.png

这种方式必须由程序员声明覆盖结构,操作系统完成自动覆盖。缺点:对用户不透明,增加了用户编程负担。覆盖技术只用于早期的操作系统中,现在已成为历史。

3.2、交换技术

交换(对换)技术的设计思想:内存空间紧张时,系统将内存中某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)

image.png

中级调度(内存调度),就是要决定将哪个处于挂起状态的进程重新调入内存。暂时换出外存等待的进程状态为挂起状态(挂起态,suspend),挂起态又可以进一步细分为就绪挂起、阻塞挂起两种状态

问题:

  • 应该在外存(磁盘)的什么位置保存被换出的进程?
    • 具有对换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分

    • 文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式;

    • 对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式(学过文件管理章节后即可理解)。

    • 总之,对换区的I/0速度比文件区的更快。

  • 什么时候应该交换?
    • 交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程;如果缺页率明显下降,就可以暂停换出。
  • 应该换出哪些进程?
    • 可优先换出阻塞进程;可换出优先级低的进程;为了防止优先级低的进程在被调入内存后很快又被换出,有的系统还会考虑进程在内存的驻留时间...。(注意,PCB会常驻内存,不会被换出外存)

4、内存分配--内存的连续分配管理--分区存储管理

连续分配:指为用户进程分配的必须是一个连续的内存空间。

分区存储管理是满足多道程序设计的最简单的一种存储管理方法。 它允许多个应用程序(早期分区存贮管理中称为作业)共享主存空间,这些程序在主存是以划分分区而共存的。分区存储管理技术从早期的固定式分区方法(单一连续分配到固定式分区)发展成为动态分区方法。

4.1、单一连续分配

在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。这种分配方式的内存中只能有一道用户程序,用户程序独占整个用户区空间。

image.png

  • 优点: 实现简单,无外部碎片,可以采用覆盖技术扩充内存,不一定需要采取内存保护(eg:早期的PC操作系统MS-DOS)。
  • 缺点:只能用于单用户、单任务的操作系统中,有内部碎片(分配给某进程的内存区域中,如果有些部分没有用就是“内部碎片”),存储器利用率极低。

4.2、固定分区分配

20世纪60年代出现了支持多道程序的系统,为了能在内存中装入多道程序,且这些程序之间又不会相互干扰,于是将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种可运行多道程序的内存管理方式。

固定分区分配可以分为:

  • 分区大小相等:缺乏灵活性,但是很适合用于用一台计算机控制多个相同对象的场合(比如:钢铁厂有n个相同的炼钢炉,就可把内存分为n个大小相等的区域存放n个炼钢炉控制程序)

  • 分区大小不等:增加了灵活性,可以满足不同大小的进程需求。根据常在系统中运行的作业大小情况进行划分(比如:划分多个小分区、适量中等分区、少量大分区)

image.png

操作系统应该怎样记录内存中各个分区的空闲状态和分区情况?

以分区大小不等的固定分区分配为例,如上右图。操作系统需要建立一个数据结构——分区说明表,来实现各个分区的分配与回收。每个表项(记录)对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态(是否已分配)。

image.png

当某用户程序要装入内存时,由操作系统内核程序根据用户程序大小检索该表,从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为"已分配"

  • 优点:实现简单,无外部碎片。
  • 缺点:
    • 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能;
    • 会产生内部碎片,内存利用率低。

4.3、动态分区分配【重点】

动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要,所以内存中分区的大小是不等的。因此系统内存分区的大小和数目是可变的。

eg:假设某计算机内存大小为64MB,系统区占用低端8MB主存区,用户区共56M...,在动态分区方案中,系统初启时在内存高址部分有空闲区。当程序序列进入主存后占用主存高址区,直到所剩空闲区不能装下任何一个程序时为止,如下所示

image.png

当系统运行一段时间后,程序陆续完成而释放其所在的内存区域,于是在内存中形成一些空闲区。这些空闲区又可以被其他程序使用,但由于空闲区和程序请求的大小不一定正好相等,因而这样剩余的空闲区域变得更小。当系统运行相当长一段时间后, 主存中会出现一些更小的空闲区,这将造成主存空间的浪费

在动态分区的分配方法中,对用户程序进行动态分配并实现动态地址映射。一般通过基址寄存器实现动态再定位。基址寄存器用来存放一个程序在主存中所占分区的首地址,相当于重定位寄存器的作用。当相应进程运行时,CPU每次产生的逻辑地址都要加上这个基址寄存器的内容作为访问主存的物理地址

问题1:这种动态分区方式下系统要用什么样的数据结构记录内存的使用情况?

两种常用的数据结构:

  • 空闲分区表:每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息
  • 空闲分区链:每个分区的起始部分和末尾部分分别设置前向指针和后向指针。起始部分处还可记录分区大小等信息

举例:

image.png

问题2.当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?

比如:

image.png

把一个新作业装入内存时,须按照一定的动态分区分配算法,即从空闲分区表(或空闲分区链)中选出一个分区分配给该作业。由于分配算法算法对系统性能有很大的影响,因此人们对它进行了广泛的研究。下个小节会介绍四种动态分区分配算法...

问题3.如何进行分区的分配与回收操作?假设系统采用的数据结构是“空闲分区表”如何分配和回收?

主存分配包括分配和回收主存块两个功能:

分配主存块的功能是

依申请者所要求的主存区的大小,在空闲区表或链表中找一个足以满足此要求的可用空闲区。对每一个查寻到的空闲区将其大小与所申请的大小进行比较,若该空闲区小于所要求的大小则继续在表或链表中查找,否则就找到了所需的空闲区。这时有两种情况:

  • 空闲区与要求的大小相等,则将该空闲区分配并从表或链表中摘除;
  • 空闲区的大小大于所要求的大小,这时将空闲区分为两部分:一部分成为已分配区,建立已分配区的描述器,剩下部分仍为空闲区,修改其分区表的信息。

回收分区的主要工作是:

检查释放分区(即为回收分区)在主存中的邻接情况,它的上、下是否有邻接的空闲区,如有则合并,使之成为一个连续的空闲区,而不是许多零散的小的部分,然后修改空闲分区表的信息。若回收分区不与任何空闲区相邻接,这时应建立一个新的空闲区,并加入到空闲区表或链表中去。

下面演示具体如何分配?

情况1:为进程5分配大小为20M的1号空闲分区,由于该空闲分区比进程5所要的空间大,所以直接修改分区1的大小和起始地址即可。

image.png

情况2: 为进程5分配大小刚好为4M的3号空闲分区,由于该空闲分区和进程5所要的空间一样大,所以刚好满足后,空闲的分区就少了一个,于是删除一个对应的表项

image.png

如何回收?

情况1: 回收区的后面有一个相邻的空闲分区,如下所示,当进程4被回收以后,两个相邻的空闲分区合并即可,并更改对应分区的大小和起始地址

image.png

情况2: 回收区的前面有一个相邻的空闲分区,如下所示,当进程3被回收以后,两个相邻的空闲分区同样合并即可,并更改对应分区的大小和起始地址

image.png

情况3: 回收区的前、后有一个相邻的空闲分区,如下所示,当进程4被回收以后,三个相邻的空闲分区同样合并即可,并更改对应分区的个数、大小和起始地址

image.png

情况4: 回收区的前、后没有相邻的空闲分区,如下所示,当进程2被回收以后,就会多一个空闲分区,并更改对应分区的个数、大小和起始地址

image.png

新增一个表项

注:各表项的顺序不一定按照地址递增顺序排列,具体的排列方式需要依据动态分区分配算法来确定。

动态分区分配补充:碎片问题以及拼接技术

分区存储管理技术能满足多道程序设计的需要,但它也存在着一个非常严重的碎片问题。所谓碎片是指在已分配区之间存在着的一些没有被充分利用的空闲区。

在按区分配方法中,根据用户申请按区分配主存,会把主存越分越零碎。在系统运行一段时间后, 甚至会出现这样的局面:一个程序申请一定数量的内存时,虽然此时空闲区的总和大于该程序所要的主存容量,但却没有单个的空闲区大到足够装下这个程序。这时,分布在主存各处的空闲区占据了相当数量的空间,但这些空间不能满足一般用户程序的需要,这就造成了主存空间的浪费。

动态分区分配没有内部碎片,但是有外部碎片【静态分区产生内部碎片】。

  • 内部碎片:分配给某进程的内存区域中,内存有些部分没有用上。
  • 外部碎片:是指内存中的未分配的某些空闲分区,由于太小而难以利用。

如果内存中空闲空间的总和本来可以满足某进程的要求,但由于进程需要的是一整块连续的内存空间,因此这些“碎片”不能满足进程的需求。可以通过紧凑(拼凑,Compaction) /拼接技术来解决外部碎片。

所谓拼接技术是指移动存储器中某些已分配区中的信息,使本来分散的空闲区连成一个大的空闲区,分区分配中的存储区拼接如下图所示。拼接时机的选择一般有以下两种方案

  • 第一种方案是在某个分区回收时立即进行拼接,于是在主存中总是只有一个连续的空闲区而无碎片,但这时的拼接频率过高,系统开销加大;

  • 第二种方案是当找不到足够大的空闲区,而空闲区的存储容量总和却可以满足程序需要时进行拼接,这样,拼接的频率比第方案要小得多,但空闲区的管理稍为复杂些。

image.png

拼接技术的缺点如下

  • 消耗系统资源,因为移动已分配区信息要花费大量的CPU时间。
  • 当系统进行拼接时,它必须停止所有其他的工作。对交互作用的用户,可能导致响应时间不规律;对实时系统的紧迫任务而言,由于不能及时响应,可能造成严重后果。
  • 拼接要消耗大量的系统资源,且有时为拼接所花费的系统开销要大于拼接所得到的效益,因而这种方法的使用受到了限制。

5、动态分区分配策略

动态分区分配算法:在动态分区分配方式中,当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?【放置策略】

5.1、首次适应算法(First Fit)

算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。

如何实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区,然后更新空闲分区链,进行下一个进程的匹配

如下进程5所示,从低地址开始查找,第一个20大于15m,满足分配。

image.png

5.2、最佳适应算法(Best Fit)

算法思想: 由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即优先使用更小的空闲区

如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。

如下进程6所示,从链头开始查找,第二个10大于9m,满足分配。

image.png

缺点: 每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片,造成内存空间的浪费

5.3、最坏适应算法(Worst Fit)

又称最大适应算法(Largest Fit)

算法思想: 为了解决最佳适应算法的问题--即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。

如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。

缺点: 每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。

5.4、邻近适应算法(Next Fit)

算法思想: 首次适应算法对于每个进程来说都是每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。

如何实现: 空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。

image.png

首次适应算法每次都要从头查找,每次都需要检索低地址的小分区。但是这种规则也决定了当低地址部分有更小的分区可以满足需求时,会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来(最佳适应算法的优点)、

邻近适应算法的规则可能会导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用,划分为小分区,最后导致无大分区可用(最大适应算法的缺点)

5.5、总结

image.png