RT_Thread应用17—内存管理1

第二十四章 内存管理(第一部分)

在这里插入图片描述

一、基本概念

在这里插入图片描述
计算机系统中,变量、中间数据一般存放在 RAM 中,只有在实际使用时才将它们从 RAM 调入到 CPU 中进行运算

1、为什么不用 C 标准库中的内存管理函数?

ANS: 在C中应用malloc()和 free()这两个函数动态的分配内存和释放内存。但在嵌入式实时操作系统中,调用 malloc()和 free()却是危险的。原因:

  • 这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的 RAM 不足。
  • 它们的实现可能非常的大,占据了相当大的一块代码空间。
  • 他们几乎都不是线程安全的。
  • 它们并不是确定的,每次调用这些函数执行的时间可能都不一样。
  • 它们有可能产生碎片。
  • 这两个函数会使得链接器配置得复杂。
  • 如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为 debug 的灾难 。
2、内存管理特点

前提:

  • 由于实时性的要求,所有的内存都需要用户参与分配,直接操作物理内存,所分配的内存不能超过系统的物理内存,所有的系统堆栈的管理,都由用户自己管理。
  • 在嵌入式实时操作系统中,对内存的分配时间要求更为苛刻,分配内存的时间必须是确定的。
    因此:

ANS:1)分配内存的时间必须是确定的。内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。
————————————
这样对实时操作系统来说,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时线程对外部事件的响应也将变得不可确定。

  • 2)随着内存不断被分配和释放,整个内存区域会产生越来越多的碎片(因为在使用过程中,申请了一些内存,其中一些释放了,导致内存空间中存在一些小的内存块,它们地址不连续,不能够作为一整块的大内存分配出去),
    -——————————
    系统中还有足够的空闲内存,但因为它们地址并非连续,不能组成一块连续的完整内存块,会使得程序不能申请到大的内存,这样,一定会在某个时间,系统已经无法分配到合适的内存了,导致系统瘫痪。
    ——————————
    对于通用系统而言,这种不恰当的内存分配算法可以通过重新启动系统来解决 (每个月或者数个月进行一次),但是对于那些需要常年不间断地工作于野外的嵌入式系统来说,就变得让人无法接受了。

  • 3)嵌入式系统的资源环境也是不尽相同,有些系统的资源比较紧张,只有数十 KB 的内存可供分配,而有些系统则存在数 MB 的内存,如何为这些不同的系统,选择适合它们的高效率的内存分配算法,就将变得复杂化。

  • RT-Thread 的内存管理模块通过对内存的申请、释放操作,来管理用户和系统对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。

对于内存管理认识可以参考
https://blog.csdn.net/gschen_cn/article/details/102794597
https://blog.csdn.net/gschen_cn/article/details/102795697

3、RT-Thread 的内存管理模块的算法

RT-Thread 的内存管理模块的算法总体上可分为两类:静态内存管理与动态内存管理.
在这里插入图片描述
还有一种:针对多内存堆的分配情况(memheap 管理算法)
在这里插入图片描述

4、RT-Thread 的程序内存分布

一般 MCU 包含的存储空间有:片内 Flash 与片内 RAM,RAM 相当于内存,Flash 相当于硬盘。

  • stm32,keil编译
    在这里插入图片描述
    上图提到的 Program Size 包含以下几个部分:
    (1)Code:代码段,存放程序的代码部分;
    (2)RO-data:只读数据段,存放程序中定义的常量;
    (3)RW-data:读写数据段,存放初始化为非 0 值的全局变量;
    (4)ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量;
    编译完工程会生成一个. map 的文件,该文件说明了各个函数占用的尺寸和地址,在文件的最后几行也说明了上面几个字段的关系:
    在这里插入图片描述
  • 程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。
    如图中左图所示,是可执行映像文件烧录到 STM32 后的内存分布,它包含 RO 段和 RW 段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。
    在这里插入图片描述
  • STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。

二、运作机制

首先,需要考虑周全。在使用内存分配前,必须明白自己在做什么,这样做与其它的方法有什么不同,特别是会产生哪些负面影响,在自己的产品面前,应当选择哪种分配策略。
在这里插入图片描述

三、静态内存管理

内存池(Memory Pool)

  • 概念:是一种用于分配大量大小相同的小内存对象的技术。它可以极 大加快内存分配/释放的速度。

  • 步骤:
    物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个大小相同的空闲内存块组成。
    一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块。在这里插入图片描述
    内核负责给内存池分配内存池对象控制块,它同时也接收用户线程的分配内存块申请,当获得申请信息后,内核就可以从内存池中为线程分配内存块。内存池一旦初始化完成,内部的内存块大小将不能再做调整在这里插入图片描述

  • 内存块分配机制
    -内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表)。每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。从下图中可以看到,物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个空闲内存块组成,内核用它们来进行内存管理。当一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块,内存控制块的参数包括内存池名,内存缓冲区,内存块大小,块数以及一个等待线程队列。
    在这里插入图片描述
    内核负责给内存池分配内存池控制块,它同时也接收用户线程的分配内存块申请,当获得这些信息后,内核就可以从内存池中为内存池分配内存。内存池一旦初始化完成,内部的内存块大小将不能再做调整。

每一个内存池对象由上述结构组成,其中 suspend_thread 形成了一个申请线程等待列表,即当内存池中无可用内存块,并且申请线程允许等待时,申请线程将挂起在 suspend_thread 链表上。

  • 例子:

内存池的线程挂起功能非常适合需要通过内存资源进行同步的场景,例如播放音乐时,播放器线程会对音乐文件进行解码,然后发送到声卡驱动,从而驱动硬件播放音乐。
在这里插入图片描述
当播放器线程需要解码数据时,就会向内存池请求内存块,如果内存块已经用完,线程将被挂起,否则它将获得内存块以放置解码的数据;
而后播放器线程把包含解码数据的内存块写入到声卡抽象设备中 (线程会立刻返回,继续解码出更多的数据);
当声卡设备写入完成后,将调用播放器线程设置的回调函数,释放写入的内存块,如果在此之前,播放器线程因为把内存池里的内存块都用完而被挂起的话,那么这是它将被将唤醒,并继续进行解码。

四、动态内存管理

动态内存管理是一个真实的堆(Heap)内存管理模块。

  • 解释:
  • 在这里插入图片描述
    小堆内存管理模块主要针对系统资源比较少,一般用于小于 2M 内存空间的系统;而
    SLAB 内存管理模块则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。

1、小堆内存管理

解析:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
每个内存块(不管是已分配的内存块还是空闲的内存块)都包含一个数据头。
在这里插入图片描述
内存管理的在表现主要体现在内存的分配与释放上,例子:

  • 分配

在这里插入图片描述
如图所示为小内存管理算法链表结构示意图的内存分配情况,空闲链表指针 lfree 初始指向 32 字节的内存块。当用户线程要再分配一个 64 字节的内存块时,但此 lfree 指针指向的内存块只有 32 字节并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128 字节时,它满足分配的要求。因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块
(52 字节)继续留在 lfree 链表中。
在这里插入图片描述
在每次分配内存块前,都会留出 **12 字节**数据头用于 magic,used 信息及链表节 点使用。返回给应用的地址实际上是这块内存块 12 字节以后的地址,而数据头部分是用户 永远不应该改变的部分。(注:12 字节数据头长度会与系统对齐差异而有所不同)

  • 释放

释放时则是相反的过程,分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块。

2、 SLAB 内存管理模块

SLAB 分配器会根据对象的类型(主要是大小)分成多个区(zone),也可以看成每类对象有一个内存池,
在这里插入图片描述
一个 zone 的大小在 32k ~ 128k 字节之间,分配器会在堆初始化时根据堆的大小自动调整。系统中最多包括 72 种对象的 zone最大能够分配 16k 的内存空间,如果超出了 16k那么直接从页分配器中分配。每个 zone 上分配的内存块大小是固定的,能够分配相同大小内存块的 zone 会链接在一个链表中,而 72 种对象的 zone 链表则放在一个数组(zone array)中统一管理。
在这里插入图片描述

  • 动态内存器2种操作
    1)内存分配

假设分配一个 32 字节的内存,slab 内存分配器会先按照 32 字节的值,从 zone array 链表表头数组中找到相应的 zone 链表。如果这个链表是空的,则向页分配器分配一个新的 zone,然后从 zone 中返回第一个空闲内存块。如果链表非空,则这个 zone 链表中的第一个 zone 节点必然有空闲块存在(否则它就不应该放在这个链表中),那么就取相应的空闲块。如果分配完成后,zone 中所有空闲内存块都使用完毕,那么分配器需要把这个 zone 节点从链表中删除。

(2)内存释放

分配器需要找到内存块所在的 zone 节点,然后把内存块链接到 zone 的空闲内存块链表中。如果此时 zone 的空闲链表指示出 zone 的所有内存块都已经释放,即 zone 是完全空闲的,那么当 zone 链表中全空闲 zone 达到一定数目后,系统就会把这个全空闲的 zone 释放到页面分配器中去。

3、 memheap 管理算法

  • 概念:
    -memheap 管理算法适用于系统含有多个地址不连续的内存堆。 使用 memheap
    内存管理可以简化系统存在多个内存堆时的使用:当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的 memheap
    初始化,并开启 memheap 功能就可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配。

  • memheap 工作机制如下图所示,在这里插入图片描述
    应用程序不用关心当前分配的内存块位于哪个内存堆上,就像是在操作一个内存堆。
    在这里插入图片描述

五、内存管理的应用场景

  • 内存管理的主要工作是动态划分并管理用户分配好的内存区间,主要是在用户需要使用大小不等的内存块的场景中使用,当用户需要分配内存时,可以通过操作系统的动态内存申请函数索取指定大小内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。

  • 静态内存管理是当用户需要使用固定长度的内存时,可以使用静态内存分配的方式获取内存,一旦使用完毕,通过静态内存释放函数归还所占用内存,使之可以重复使用。

  • 例子
    定义一个 float 型数组:float Arr[],数组需要多大?

  • 静态内存:分配固定大小。缺点:会浪费大量的内存空间

  • 动态内存:在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法,是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。

参考文献:
1、[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》
2、《RT-THREAD 编程指南》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值