内存管理

存储管理的功能

  1. 内存分配
    为每个进程分配一定的内存空间
  2. 地址映射
    把程序中的相对地址转换为内存的物理地址
  3. 内存保护
    检查地址是否合理,防止越界访问
  4. 内存扩充
    采用虚拟内存,解决内存和进程“求大于供”的问题

内存分配

连续分配方式

将一个连续的内存空间分配给程序,这就是连续分配方式。

单一连续分配

这是应用于早期计算机的一种连续内存分配方式,CPU一次只能执行一个进程。这时的内存分为两个区域:系统区和用户区。

  • 优点:简单便于管理
  • 缺点:只支持单道程序,内存浪费,且用户程序很容易破坏操作系统

固定分区分配

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

对于内存用户空间的划分,有如下两种方法:

  • 分区大小相等,即所有的内存分区大小相等。缺点是缺乏灵活性,即当程序太小时,会造成内存资源的浪费,程序太大时,一个分区由不足以装入该程序,只是该程序无法运行。
  • 分区大小不等,把内存区划分成含有多个较小的分区、适量中等分配和少量大分区,这样,便可根据程序的大小为之分配适当的分区。
    为了便于内存分配,将分区按大小进行排队,并为之建立一张分区使用表,其中各表项包括每个分区的起始地址、大小、状态(是否已分配),当有一个程序需要装入时,由内存分配程序检索该表,从中找出一个能满足要求的,尚未分配的分区,将之分配给该程序,然后将该表项中的状态设置为已分配,若未找到大小足够的分区,则拒绝为该用户分配内存。
    在这里插入图片描述
    单一连续分配和固定分区分配的比较:
    在这里插入图片描述

动态分区分配

数据结构
  • 空闲分区表
    在这里插入图片描述

  • 空闲分区链
    在这里插入图片描述

分区分配算法
  • 首次适应算法(First Fit)
    空闲分区链以地址递增的次序链接。分配时,从链首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止;再按作业的大小,从该分区中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中。若从链首直至链尾都不能找到一个能满足要求的分区,则失败返回
    在这里插入图片描述
    优点:简单,容易实现
    不足:内存资源利用率低。

  • 循环首次适应算法(Next Fit)
    由首次适应算法演变而成,分配内存时不是从链首进行查找可以分配内存的空闲分区,而是每一次从上次找到的下一个分区开始查找。直到找到一个能满足要求的分区。能使空闲分区变得均匀,但会缺乏大的空闲分区
    在这里插入图片描述

  • 最佳适应算法(Best Fit)
    将所有的空闲分区按其容量以从小到大的顺序形成一空闲分区链,第一次找到的空闲区必然是最佳的
    在这里插入图片描述
    优点:第一次找到的空闲分区是大小最接近待分配内存作业大小的;
    缺点:产生大量难以利用的外部碎片

  • 快速适应算法(Quick Fit)
    该算法又称为分类搜索法,是将空闲分区容量大小进行分类,对于每一类具有相同容量的所有空闲分区,单独设立一个空闲分区链表,系统中存在多个空闲分区链表,同时在内存中设立一张管理索引表,该表的每一项对应了一种空闲分区类型,并记录了该类型空闲分区链表表头的指针。
    该算法的优点是查找效率高,仅需根据进程的长度,寻找到能容纳它的最小空闲区链表,并取下第一块进行分配即可。该算法在进行空闲分区分配时,不会对任何分区产生分割,所以能保留大的分区,满足对大空间的需求,也不会产生内存碎片。
    但是在分区归还主存时算法复杂,系统开销大。

分配与回收操作
  • 分配内存
    设请求的分区大小:u.size
    设空闲分区大小: m.size
    不可再切割的剩余分区大小:size
    如果m.size- u.size≤ size将整个分区分配给请求者,否则剩余部分留在空闲分区链(表)中。
    将分区的首址返回给调用者
    在这里插入图片描述
  • 回收操作
    当进程释放内存时,系统根据回收区的首值,
    从空闲区链(表)中找到相应的插入点,回收区可能出现四种情况:
    (1)与插入点的前一个空闲区F1相邻接。
    (2)与插入点的后一空闲分区F2相邻接。
    (3)同时与插入点的前、后两个分区邻接。
    (4)既不与F1邻接,又不与F2邻接。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

动态重定位分区分配

引入

在连续分配方式中,必须把一个系统或用户程序装入一连续的内存空间。如果在系统中只有若干个小的分区,即使它们的容量总和大于要装入的程序,但由于这些分区不相邻接,也无法把该程序装入内存。这种不能被利用的小分区称为“零头”或“碎片”。
在这里插入图片描述
当内存中出现几个互不邻接的小分区,它们单独的容量不能满足作业的大小,但它们的容量总和大于作业的要求时,若想把作业装入,可采用的一种方法是:将内存中的所有作业进行移动,使它们全部相邻接,这样,即可把原来分散的多个小分区拼接成一个大分区,这时就可把作业装入该区。这就引入了拼接技术来实现动态重定位分区分配。

实现

拼接(紧凑/紧缩): 通过移动内存中作业的位置,然后把原来多个分散的空闲小分区拼接成一个大分区的方法。
所谓拼接是指移动内存(存储器)中所有已分配区到内存的一端,使本来分散的小空闲区连成一个大的空闲区。
在这里插入图片描述

注意:

  • 由于经过紧凑后的某些用户程序在内存中的位置发生了变化,此时若不对程序和数据的地址加以修改(变换),则程序必将无法执行。
    为此,在每次“紧凑”后,都必须对移动了的程序或数据进行重定位。

拼接时机(什么时候拼接):

  • 第一种方案是在某个分区回收时立即进行拼接,这样在内存中总是只有一个连续的空闲区。但由于拼接很费时间,拼接频率过高会使系统开销加大。
  • 第二种方案是当找不到足够大的空闲区且空闲区的总容量可以满足作业要求时进行拼接。拼接的频率比第一种要小得多,但空闲区的管理稍微复杂一些。

关于地址:

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

在这里插入图片描述

伙伴算法

伙伴算法的引入也是为了解决连续分配方式中存在的碎片问题。Linux 便是采用这著名的伙伴系统算法来解决外部碎片的问题。
  伙伴系统规定,无论已分配分区还是空闲分区,其大小均为2的k次幂,k为整数,1<= k <= m,其中,21表示分配的最小分区的大小,2m表示分配的最大分区的大小,通常2m是整个可分配内存的大小。假设系统开始时的初始容量为2m个字,由于不断切分,可能会形成若干个不连续的空闲分区,将这些空闲分区根据分区的大小进行分类,对于每一类具有相同大小的所有空闲分区,单独设立一个空闲分区双向链表。这样,不同大小的空闲分区形成了k个空闲分区链表。
  当需要为进程分配一个长度为n的存储空间时,首先计算一个i值,使2i-1 < n <= 2i,然后,在空闲分区大小为2i的空闲分区链表中查找,若找到,即把该空闲分区分配给进程,否则,表明2i的空闲分区已经耗尽,在大小为2i+1的空闲分区链表中查找,若存在,则将该空闲分区分为两个大小为2i的分区,一个用于分配,一个加入到大小为2i的空闲分区链表中,若还是不存在,则继续在大小为2i+2的空闲分区链表中查找,若存在,则将空闲分区进行两次分割,一次分割为两个大小为2i+1的空闲分区,一个加入到大小为2i+1的空闲分区链表中,另外一个继续进行分割,分成两个大小2i的空闲块,一个用于分配,另外一个加入到大小为2i的空闲分区链表中,以此类推。在最坏的情况下,可能需要对2k的空闲分区进行k此分割才能得到所需分区。
  当回收空闲分区时,也需要经过多次合并,如回收大小为2i的空闲分区时,若事先已经存在2i的空闲分区,则应将其与伙伴分区合并为一个大小为2i+1的空闲分区,若事先已存在2i+1的空闲分区,则再次进行合并,合并为2i+2的分区,以此类推。
  伙伴算法虽然能够完全避免外部碎片的产生,但这恰恰是以产生内部碎片为代价的。

例子: 假设系统中有 1MB 大小的内存需要动态管理,按照伙伴算法的要求:需要将这1M大小的内存进行划分。这里,我们将这1M的内存分为 64K、64K、128K、256K、和512K 共五个部分,如下图所示
在这里插入图片描述

  1. 此时,如果有一个程序A想要申请一块45K大小的内存,则系统会将第一块64K的内存块分配给该程序(产生内部碎片为代价),如图b所示;
  2. 然后程序B向系统申请一块68K大小的内存,系统会将128K内存分配给该程序,如图c所示;
  3. 接下来,程序C要申请一块大小为35K的内存。系统将空闲的64K内存分配给该程序,如图d所示;
  4. 之后程序D需要一块大小为90K的内存。当程序提出申请时,系统本该分配给程序D一块128K大小的内存,但此时内存中已经没有空闲的128K内存块了,于是根据伙伴算法的原理,系统会将256K大小的内存块平分,将其中一块分配给程序D,另一块作为空闲内存块保留,等待以后使用,如图e所示;
  5. 紧接着,程序C释放了它申请的64K内存。在内存释放的同时,系统还负责检查与之相邻并且同样大小的内存是否也空闲,由于此时程序A并没有释放它的内存,所以系统只会将程序C的64K内存回收,如图f所示;
  6. 然后程序A也释放掉由它申请的64K内存,系统随机发现与之相邻且大小相同的一段内存块恰好也处于空闲状态。于是,将两者合并成128K内存,如图g所示;
  7. 之后程序B释放掉它的128k,系统也将这块内存与相邻的128K内存合并成256K的空闲内存,如图h所示;
  8. 最后程序D也释放掉它的内存,经过三次合并后,系统得到了一块1024K的完整内存,如图i所示。

离散分配方式

引入

基本思想:将一个进程分散的装入不相邻的分区中

连续的分配方式会产生很多碎片。离散的分配方式是将进程、资源装入不相邻的多个分区的内存分配方式。这种分配方式按照离散分配的基本单位是“页”还是“段”,分为分页存储管理、分段存储管理以及段页式存储管理。

虚拟内存

引入

常规存储器存在的问题:

  • 一次性。即作业在运行前需一次性全部装入内存。大作业要求的内存空间超过了内存容量不能全部被装入致使该作业无法运行。
  • 驻留性。作业装入内存后,便一直驻留在内存中,直至作业运行结束。 有大量作业要求运行,但只能将少数作业装入内存让它们先运行,而将其它大量的作业留在外存上等待。

为了解决常规存储器存在的问题,引入了虚拟存储器。
虚拟存储器,是指具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储器系统。其逻辑容量由内存和外存之和所决定,其运行速度接近于内存速度,而每位的成本却又接近于外存。
虚拟内存是内存和磁盘交换的第二个媒介。虚拟内存是指把磁盘的一部分作为假象内存来使用。这与磁盘缓存是假象的磁盘(实际上是内存)相对,虚拟内存是假象的内存(实际上是磁盘)。
虚拟内存是计算机系统内存管理的一种技术,它使得应用程序认为它拥有连续可用的内存(一个完整的地址空间)。但是实际上它通常被分割成多个物理碎片,还有部分存储在外部磁盘管理器上,必要时进行数据交换。
通过借助虚拟内存,在内存不足时仍然可以运行程序。比如在只剩5MB内存空间的情况下仍然可以运行10MB的程序。由于CPU只能执行加载到内存中的程序,因此,虚拟内存的空间就需要和内存中的空间进行置换(swap),然后运行程序。

在这里插入图片描述

虚拟内存与内存的交换方式

虚拟内存的方法有分页式和分段式两种。Windows采用的是分页式。该方法在不考虑程序构造的情况下,把运行的程序按照一定大小的页进行分割,并以页为单位进行置换。

在这里插入图片描述

虚拟内存的解决方法

虚拟内存实现进程并发借助两个局部性原理:

  • 时间局限性。某指令一旦执行,则不久后该指令可能再次执行;某数据被访问过,则不久后该数据可能再次被访问
  • 空间局限性。程序在一段时间内所访问的地址,可能集中在一定的范围之内,其典型的情况便是程序的顺序执行

基于局部性原理:

  • 程序在运行前,没必要全部装入内存,仅需要将当前运行的页(段)装入内存即可
  • 运行时,如果访问的页(段)在内存中,则继续执行;如果访问的页(段)未在内存中(缺页或缺段),则利用OS的请求调页(段)功能,将页(段)调入内存
  • 如果内存已满,则利用OS的页(段)置换功能,按照某种置换算法将内存中的某页(段)调至外存,从而调入所需的页(段)
虚拟内存的空间划分

在这里插入图片描述

虚拟内存的实现

虚拟内存的实现有以下三种方式:

  1. 请求分页存储管理
  2. 请求分段存储管理
  3. 请求段页式存储管理

不管哪种方式,都需要有一定的硬件支持。一般需要的支持有以下几个方面:

  • 一定容量的内存和外存
  • 页表机制(或段表机制),作为主要的数据结构
  • 中断机构,当用户程序要访问的部分尚未调入内存,则产生中断
  • 地址变换机构,逻辑地址到物理地址的变换

基本分页存储管理方式

将一个进程的逻辑地址空间分成若干个大小相等的片,称为页面或页,并为各页编号,从0开始,如第0页、第1页等。
把内存空间分成与页面相同大小的若干个存储块,称为块或页框,也加以编号,如0#、1#块等。
以块为单位将进程中的若干个页分别装入到多个可以不相邻接的物理块中。

基本要素
  1. 页面碎片
    由于进程的最后一页经常不满一块而形成可不可利用的碎片,称之为“页内碎片”。离散式存储方式也会产生碎片
  2. 页面大小
    通常512B—8KB
    页面太小:页内碎片小,提高内存利用率,但页表过长,占内存;降低对换效率。
    页面太大:提高了对换速度。但页内碎片大,降低了内存利用率。
  3. 页表
    系统为了能在内存中找到每个页面对应的物理块而为进程建立的一张页面映像表,简称页表。
    页表作用:实现从页号到物理块号的地址映射。
    在这里插入图片描述
    在这里插入图片描述
工作过程

基本任务:实现从逻辑地址到物理地址的转换。
实际上是将逻辑地址中的页号,转换为内存中的物理块号。地址变化任务是借助于页表来完成的。
无快表*:

在这里插入图片描述
在这里插入图片描述

进程未执行时,页表的始址和页表长度存放在本进程PCB中。当调度到进程时装入页表寄存器中。

CPU存取一个数据时要两次访问内存,第一次是访问页表,找到指定页的物理块号,再将块号与页内地址W拼接形成物理地址。第二次访问内存是从所得地址中获得所需数据(或向此地址中写入数据)。

有快表(TLB):

为提高地址变换速度:增设一个具有并行查询能力的高速缓冲寄存器,称为“联想寄存器” 或 “快表” ,用于存放当前访问的页表项。

主要用于解决:

  • CPU存取一个数据时要两次访问内存(访问页表和访问内存)
  • 页表过大

CPU给出有效地址,由地址变换机构自动地将页号p送入高速缓冲存储器,并将此页号与高速缓存中的所有页号进行比较,若有与此相匹配的页号,则表示所要访问的页表项在快表中。于是,可直接读出该页所对应的物理块号,并送物理地址寄存器中。如在快表中末找列对应的页表项,还须再访问内存中的页表,找到后,把从页表项中读出物理块号送地址寄存器;同时,还将此页表项存入快表中的一个寄存器单元中,亦即置新修改快表、但如果联想存储器已满,则OS必须找到一个老的且已被认为不再需要的页表项将它换出。
在这里插入图片描述

页面置换算法

在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法

最佳置换算法(OPT)

实现原理:每次选择未来长时间不被访问的或者以后永不使用的页面进行淘汰。

举例
假定系统为某进程分配了三块物理块,并有以下页面:
7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
程序运行时,先将7,0,1三个页面装入内存。
之后,当进程要访问页面2的时候,将会产生缺页中断。此时根据最佳置换算法,因为页面7要在第18次才能访问,页面0在第5次访问,页面1在第14次访问,页面7最久不被使用,所以将页面7淘汰;
当进程0要访问时,因为它已存在在内存所以不必产生缺页中断;
当页面3要访问时,又引起缺页中断淘汰1;
依次类推直到最后一个页面访问完。下图为采用最佳置换算法的置换图。由图可得,采用最佳置换算法发生了6次缺页中断。

在这里插入图片描述
缺点:最佳置换算法是一种理想化算法,具有较好的性能,但是实际上无法实现(无法预知一个进程中的若干页面哪一个最长时间不被访问);
优点:最佳置换算法可以保证获得最低的缺页率

先进先出页面置换算法(FIFO)

实现原理:淘汰最先进入内存的页面,即选择在页面待的时间最长的页面淘汰。

举例:
依旧是上一个算法的例子
程序运行时,先将7,0,1三个页面装入内存。
之后,当进程要访问页面2的时候,将会产生缺页中断。此时根据先进先出置换算法,因为页面7是最先进入内存的,所以将页面7换出;
当进程0要访问时,因为它已存在在内存所以不必产生缺页中断;
在进程要访问页面3的时候,因为页面0是最早进入内存的,所以将页面0换出;
依次类推直到最后一个页面访问完。下图为采用先进先出置换算法的置换图。由图可得,采用最佳置换算法发生了12次缺页中断。先进先出的页面置换比最佳置换算法的页面置换正好多了一倍;
在这里插入图片描述

优点:先进先出算法实现简单,是最直观的一个算法
缺点:先进先出的性能最差,因为与通常页面的使用规则不符合,所以实际应用少

最近最久未使用置换算法(LRU)

实现原理:选择最近且最久未被使用的页面进行淘汰。

举例:
依旧是上一个算法的例子
程序运行时,先将7,0,1三个页面装入内存。
之后,当进程要访问页面2的时候,将会产生缺页中断。此时根据最近最久未使用置换算法,因为页面7是最近最久未被使用的的,所以将页面7淘汰;
当进程0要访问时,因为它已存在在内存所以不必产生缺页中断;
在进程要访问页面3的时候,因为页面1是最近最久未被使用的,所以将页面1淘汰;
依次类推直到最后一个页面访问完。下图为采用最近最久未使用的置换算法的置换图。由图可得,采用最近最久未使用置换算法发生了9次缺页中断。
在这里插入图片描述
优点:由于考虑程序访问的时间局部性,一般能有较好的性能;实际应用多
缺点:实现需要较多的硬件支持,会增加硬件成本

时钟页面置换算法(clock)

在这里插入图片描述

在这里插入图片描述

例子:
在这里插入图片描述

基本分段存储管理方法

引入

从固定分区到动态分区分配,再到分页存储管理方式,其主要动力为提高内存利用率,引入分段存储管理的目的在于满足用户在编程和使用上多方面的要求。

逻辑地址划分

在分段存储管理方式中,作业的地址空间被划分为若干段,每个段定义一组逻辑信息。每个段都从0开始编址.采用一段连续的地址空间。段的长度由相应的逻辑信息组的长度决定,因而段长不等。
整个作业的地址空间分成多个段,是二维的。段与段之间没有联系,而页与页之间是有编号的,所以说页是一维的。
在这里插入图片描述

地址转换过程

系统为每一个进程建立一张段映射表,简称段表。用于实现从逻辑段到物理内存的映射。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

可重入代码

可重入代码是一种允许多个进程同时访问的代码。为使各个进程所执行的代码完全相同,绝对不允许可重入代码在执行中有任何改变。
但事实上,大多数代码在执行时都可能有些改变,例如,用于控制程序执行次数的变量以及指针、信号量及数组等。
为此,在每个进程中,都必须配以局部数据区,把在执行中可能改变的部分拷贝到该数据区,这样,程序在执行时,只需对该数据区(属于该进程私有)中的内容进行修改,并不去改变共享的代码,这时的可共享代码即成为可重入码。

分页和分段的区别

  1. 页是信息的物理单位,分页是为由于系统管理的需要
    段是信息的逻辑单位,分段是为了满足用户的需要
  2. 页的大小固定且由系统决定
    段的长度不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时,根据信息的性质来划分
  3. 分页的作业地址空间是一维的,程序员只需利用一个记忆符,即可表示一个地址
    分段的作业地址空间则是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地值

在这里插入图片描述
在这里插入图片描述

参考资料:
微信公众号:陈同学在搬砖
动态分区分配算法
【Linux 内核】内存管理(二)伙伴算法

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值