FreeRTOS学习(二十六)——动态内存管理heap_1到heap_5

这一节来看一个平时很容易被忽略的问题:

复制代码
xTaskCreate();

xQueueCreate();

xSemaphoreCreateMutex();

这些函数创建任务、队列和互斥量时,都需要占用RAM。

那么这些内存到底从哪里来?FreeRTOS又是怎么管理这些内存的?

FreeRTOS提供了:

复制代码
heap_1

heap_2

heap_3

heap_4

heap_5

五种动态内存管理方案。

本节不深入分析源码,先搞清楚五种Heap之间最主要的区别,以及STM32项目中应该怎么选择。


一、FreeRTOS为什么需要Heap

例如创建任务:

复制代码
xTaskCreate(
    Task1,
    "Task1",
    128,
    NULL,
    2,
    NULL
);

FreeRTOS需要为这个任务分配:

复制代码
任务控制块TCB

+

任务栈

这些内存默认通过:

复制代码
pvPortMalloc();

申请。

删除任务以后,则通过:

复制代码
vPortFree();

释放。

因此FreeRTOS自己提供了一套内存管理接口:

复制代码
pvPortMalloc()

vPortFree()

而真正决定这两个函数如何工作的,就是:

复制代码
heap_x.c

二、Heap大小在哪里设置

在:

复制代码
FreeRTOSConfig.h

中通常可以看到:

复制代码
#define configTOTAL_HEAP_SIZE

例如:

复制代码
#define configTOTAL_HEAP_SIZE \
        ((size_t)(20 * 1024))

表示:

复制代码
FreeRTOS Heap

大小约20KB

创建:

复制代码
Task

Queue

Semaphore

Mutex

等动态对象时,就会消耗这部分内存。

因此:

复制代码
configTOTAL_HEAP_SIZE

并不是STM32全部RAM的大小。

而是:

分配给FreeRTOS动态内存管理使用的空间大小。

三、heap_1

heap_1.c是最简单的一种方案。

它只支持:

复制代码
申请内存

不支持:

复制代码
释放内存。

也就是说:

复制代码
pvPortMalloc();

可以使用。

但是已经申请的内存不能重新释放利用。

因此适合:

复制代码
系统启动时创建所有任务

↓

运行过程中不再删除

这种非常固定的系统。

它最大的特点就是:

复制代码
简单

确定性高

但灵活性比较差。

四、heap_2

heap_2.c支持:

复制代码
申请

+

释放

因此任务删除以后:

复制代码
内存可以重新使用。

但是它有一个明显的问题:

复制代码
相邻空闲内存块

不会自动合并。

例如:

复制代码
已用 | 空闲10B | 空闲20B | 已用

即使两个空闲区域相邻,也不会自动变成:

复制代码
空闲30B

长期频繁申请和释放不同大小的内存时:

复制代码
容易产生内存碎片。

因此现在实际工程中:

复制代码
heap_2

使用得比较少。

五、heap_3

heap_3.c比较特殊。

它直接使用标准C库:

复制代码
malloc();

free();

也就是说:

复制代码
FreeRTOS

↓

调用C库内存管理

因此实际内存管理方式取决于:

复制代码
编译器

C运行库

而不是FreeRTOS自己维护Heap数组。

这种方式虽然简单,但是:

复制代码
行为依赖具体C库

在资源受限、希望内存行为更加可控的嵌入式系统中,并不是最常见的选择。

六、heap_4

heap_4.c是实际STM32 FreeRTOS工程中非常常见的一种方案。

它支持:

复制代码
申请内存

释放内存

并且:

复制代码
能够合并相邻空闲内存块。

例如:

复制代码
空闲10B

+

空闲20B

如果两个区域相邻:

复制代码
↓

自动合并

↓

空闲30B

因此相比:

复制代码
heap_2

可以明显降低:

复制代码
内存碎片问题。

所以对于普通STM32项目:

heap_4通常是比较推荐的选择。

七、heap_5

heap_5.c的内存管理算法和:

复制代码
heap_4

比较类似。

但是它最大的特点是:

复制代码
可以管理多个不连续的内存区域。

例如某些MCU拥有:

复制代码
内部SRAM

SRAM1

SRAM2

外部SDRAM

这些RAM在地址上可能并不连续。

heap_5可以把多个区域统一交给FreeRTOS管理。

因此:

复制代码
单一连续RAM

↓

heap_4通常够用

而:

复制代码
多个不连续RAM区域

↓

可以考虑heap_5

八、五种Heap怎么记

初学阶段不用记复杂实现,只需要记住:

复制代码
heap_1

只申请
不释放

heap_2

可以申请释放
但不合并相邻空闲块

heap_3

使用标准malloc/free

heap_4

申请 + 释放
还能合并相邻空闲块

heap_5

类似heap_4
但支持多个不连续RAM区域

其中STM32普通项目中最值得重点掌握的是:

复制代码
heap_4

九、查看剩余Heap空间

FreeRTOS还提供:

复制代码
xPortGetFreeHeapSize();

可以查看当前剩余Heap。

例如:

复制代码
printf(
    "Free Heap = %lu\r\n",
    (unsigned long)xPortGetFreeHeapSize()
);

创建任务之前:

复制代码
Free Heap = 20000

创建任务以后可能变成:

复制代码
Free Heap = 19000

说明这个任务以及相关数据结构消耗了一部分Heap。

还可以使用:

复制代码
xPortGetMinimumEverFreeHeapSize();

查看系统运行以来:

复制代码
Heap最少剩余过多少。

这个值对于判断:

复制代码
configTOTAL_HEAP_SIZE

到底够不够

非常有帮助。

十、总结

这一节学习了FreeRTOS中的五种动态内存管理方案。

FreeRTOS创建任务、队列、信号量等动态对象时,会通过:

复制代码
pvPortMalloc();

申请内存。

FreeRTOS提供:

复制代码
heap_1
heap_2
heap_3
heap_4
heap_5

五种实现方式。

其中最需要记住的是:

复制代码
heap_1:只申请,不释放

heap_2:可释放,但不合并空闲块

heap_3:使用malloc/free

heap_4:可释放,并合并相邻空闲块

heap_5:支持多个不连续RAM区域

对于普通STM32 FreeRTOS项目,如果没有特殊需求:

复制代码
heap_4

通常是一个比较常用的选择。

另外,FreeRTOS Heap只是MCU RAM中的一部分:

复制代码
MCU总RAM

≠

configTOTAL_HEAP_SIZE

这一点一定不要混淆。

相关推荐
崇子嵘44 分钟前
基于zynqMP15eg的linux驱动学习
学习
LCG元2 小时前
STM32+ESP8266+MQTT 物联网气象站:从零搭建温湿度远程监测系统(附完整源码)
stm32·物联网·struts
知识分享小能手3 小时前
线性代数学习教程,从入门到精通,向量组的线性相关性 — 完整知识点梳理(7)
学习·线性代数·机器学习
Shell运维手记3 小时前
Linux 常用基础命令学习笔记
linux·运维·笔记·学习·算法·github
FakeOccupational4 小时前
【电路笔记 STM32】Cortex-M7 内核上的数据缓存(D-Cache)结构+MPU+DMA&Cache+STM32CubeMX配置
笔记·stm32·缓存
动词ing4 小时前
【学习笔记】C语言(数组指针与指针数组+字符数组+函数+参数传递+字符串作为形参+递归函数+指针函数+回调函数+结构体嵌套+内存动态分配函数)
c语言·笔记·学习
殷忆枫4 小时前
基于K210与STM32的智能垃圾分类与物联网监管系统
stm32·物联网·分类
周洲08305 小时前
STM32 GPIO 外部中断深度解析:边沿触发 / 电平触发、NVIC 优先级配置、中断嵌套实战
stm32·单片机·嵌入式硬件
LCG元5 小时前
STM32F103 CAN 总线通信实战:标准库双机通信与过滤器配置精讲
stm32·单片机·嵌入式硬件