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

这一点一定不要混淆。

相关推荐
La Pulga42 分钟前
【FreeRTOS源码学习笔记】任务切换
单片机·mcu·学习·嵌入式·freertos·实时操作系统
li星野1 小时前
【学习记录】逻辑分析仪完全入门:从品牌方案到采样率、存储深度的核心概念解析
学习
旖旎夜光2 小时前
【C++ 基础入门】类和对象(上):封装、对象模型与 this 指针
开发语言·c++·笔记·学习
WEI_YANG_JU3 小时前
初中数学教学中合作学习的研究与实践
学习
码农小韩3 小时前
FPGA学习(一)——LED点灯与开发流程
学习·fpga开发·入门·环境配置·仿真·点灯
李长渊哦4 小时前
Qoder 自定义子智能体(Subagent)学习笔记
chrome·笔记·学习
代码山河4 小时前
基本类型转换:自动类型转换与强制类型转换
java·学习·架构·教程·面向对象·项目
女神下凡4 小时前
芯参谋(85):Linux_PPI_NAND_驱动开发指南
linux·运维·arm开发·驱动开发·单片机·嵌入式硬件·设计规范
旖旎夜光5 小时前
【C++ 基础入门】命名空间、输入输出、缺省参数与函数重载
开发语言·c++·笔记·学习
m4Rk_5 小时前
【论文阅读】Agent 记忆机制(119):EvoLife——让具身 Agent 把主动问到的答案沉淀为可复用的空间经验
论文阅读·人工智能·学习·开源·github