操作系统 -- 内存管理(分配与回收)

内存的分配方式

连续内存分配

单一连续分配(过时)

在这里插入图片描述

  • 内存被分为两部分, 系统区和用户区
  • 系统区通常位于内存的低地址部分, 用域存放操作系统的相关数据. 用户区用于存放用户进程相关数据
  • 内存中只有一个用户程序, 用户程序独占整个用户空间

优点 :

  • 实现简单, 无外部碎片

缺点 :

  • 只能用于单用户, 单任务的操作系统
  • 有内部碎片, 内存的利用率极低

固态分区分配

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

主要分为两种情况 : 分区大小相等, 分区大小不等
在这里插入图片描述
在这种情况下, 操作系统需要建立一个数据结构 – 分区说明表 来实现各个分区的分配与回收;
每个表项对应一个嗯去, 通常按分区大小排序, 每个表项包括对应分区的大小, 起始地址, 状态

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

  • 当某个进行需要进行内存分配时, 操作系统根据进程的大小检索该表, 从中找到一个能满足大小的, 未分配的分区, 将其分配给该程序, 然后修该状态为已分配

优点 :

  • 实现简单, 无外部碎片

缺点 :

  • 当进行占用内存太大时, 可能所有的分区都不满足要求
  • 会产生内部碎片, 内存的利用率低

动态分区分配

这种分配方式不会预先划分内存分区, 而是再进程装入内存时, 根据进程的大小动态分区, 使分区的大小正好适合进程的需要

那么就有三个问题需要我们考虑 :

  • 系统要用什么样的数据接口记录内存的使用情况 ?
  • 当很多个内存空闲区域都能满足需求时, 应该选择哪个分区进行分配 ?
  • 如何进行分区的分配与回收操作 ?

首先考虑第一点 : 系统要用什么样的数据接口记录内存的使用情况
两种常见的数据结构 :

  • 空闲分区表
  • 空闲分区链

在这里插入图片描述
接下来考虑第二点 : 当很多个内存空闲区域都能满足需求时, 应该选择哪个分区进行分配 ?

这个就涉及分配算法了, 接下来会详细说明

接下来考虑第三点 : 如何进行分区的分配与回收操作 ?
使用图解说明, 从一个进程的内存分配到内存回收
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如何进行分区的回收呢 ?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 动态分配没有内部碎片, 但是有外部碎片
  • 内部碎片 : 分配给某进程的内存区域, 有一些部分没有用上
  • 外部碎片 : 是指内存中的某些空闲分区由于太小而难以利用
  • 如果内存中空闲的空间的总和本可以满足某进程的要求, 但是由于进程需要的是一整块连续的内存空间, 因此这些碎片不能满足进程的需求
动态分区分配算法
首次适应算法

每次都从低地址开始查找,找到第一个能满足大小的空闲分区。进行分配

最佳适应算法
  • 算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。
  • 如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。

缺点 :

  • 每次都选用最小的分区进行分配, 会留下越来越多的, 小的, 难以利用内存块, 产生了很多的外部碎片
最坏适应算法
  • 算法思想:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
  • 如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。

缺点 :

  • 每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。
邻近适应算法
  • 算法思想 :首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。

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

  • 首次适应算法每次都要从头查找,每次都需要检索低地址的小分区。

  • 但是这种规则也决定了当低地址部分有更小的分区可以满足需求时,会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来(最佳适应算法的优点)

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

综合来看,四种算法中,首次适应算法的效果反而更好

非连续内存分配

连续分配方式与非连续分配方式的区别 :

  • 连续分配方式 : 一个分区一个进程
  • 非连续分配方式 : 把一个进程分解成几块, 分别放入几个不同的分区

基础定义

  • 页面 : 将进程分割为多个固定的, 大小相同的部分
  • 页框 : 将内存分割为多个固定大小的部门

通常页面与页框的大小是相同的, 这样保证没有内部碎片, 但是进程的最后一个页面通常无法利用完整个页框,会不可避免地产生页内碎片,为了让碎片足够小,必须控制好单个页框的大小,不能过大

系统以页框为单位为各个进程分配内存空间,一个页面就对应一个页框,它具体放到哪个页框,这是随意的

地址转换

十进制地址
  • 计算逻辑地址的页号
  • 根据页号找到页号对应页面在内存中的起始地址
  • 计算逻辑地址在当前页面内的偏移量
  • 物理地址 = 起始地址 + 页内偏移量
二进制地址

在这里插入图片描述

页表

根据地址,就已经可以知道页号和页内偏移量,剩下还有一个工作就是根据页号找到页号对应页面在内存中的起始地址

每一个进程,可以维护一张页表

页表用来记录页面号(页号)与页框号(块号)的映射关系,可以根据页号找到页号指代页面在内存中对应页框的编号
在这里插入图片描述

地址转变过程

在这里插入图片描述

地址转变过程(带有快表)

在前面的基本地址变换机构中,存在两个问题:

每次存取数据都需要访问内存两次:第一次访问内存中的页表,找到块号,并将块号与偏移量拼接得到物理地址;第二次,根据物理地址访问内存中存放的数据。第二次访存肯定是不能避免的,但是第一次访存其实可以想办法避免
若多条指令涉及到的逻辑地址的页号都相同,则每次都得经历第一次访存,找到该页号对应的块号
上面这两个问题可以通过引入快表来解决。

快表又叫做联想寄存器,它是一种访问速度比内存快很多的高速缓冲存储器,用以存放访问过的页表项的副本,从而加快地址转换的过程 —— 也就是说,引入快表后,地址转换可以不需要经历第一次访存,而是直接从快表中拿到需要的页表项。与之对应的,内存中原本的页表,叫做慢表。

在这里插入图片描述

页表项

在这里插入图片描述

在这里插入图片描述

物理块号的地址=逻辑地址-页号地址=32-20=12位,因为并不是进程的每一个页面都要调入内存,所以只有部分页面有对应内存的物理块号,所以物理块号的大小(212)会小于页号大小(220)

参考

原文地址 :

  • https://blog.csdn.net/Javascript_tsj/article/details/124617376
  • https://blog.csdn.net/u014453898/article/details/108838859
  • https://segmentfault.com/a/1190000022556314
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值