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

这一点一定不要混淆。

相关推荐
香芋芋圆4 小时前
AI 冲击内卷之下,普通前端如何破局?WebGIS—— 低门槛突围赛道
前端·javascript·人工智能·学习·职场发展
点心的游戏开发世界4 小时前
Unity C# 脚本学习笔记:泛型
学习·unity·c#
tju新生代魔迷5 小时前
Verilog HDL 学习笔记(十三)| 第13章 编程语言接口(PLI)
笔记·学习
智购科技自动贩卖机6 小时前
自动售货机嵌入式状态机设计实战:从45个事件源到层次型状态机的工程重构
大数据·人工智能·stm32·物联网·重构·硬件架构
长江后浪博客6 小时前
ESP32-SOLO-1 在 ESP-IDF v5.5 下启动不断重启问题分析与解决
单片机·嵌入式硬件·嵌入式·esp32·freertos·esp-idf
单片机仿真设计7 小时前
【proteus仿真】基于 STM32 单片机智能仓库设计(仿真图+程序)
stm32·单片机·嵌入式硬件·proteus·毕设
星游路7 小时前
单片机·电子时钟
单片机·嵌入式硬件
我爱cope8 小时前
【计算机网络 | 网络层11:ICMP 协议:Ping 和 Traceroute 的底层原理】
网络·学习·计算机网络
问天_观心8 小时前
大模型微调学习(一)
开发语言·人工智能·学习·语言模型·github
苏生Susheng9 小时前
【软件实施】Linux系统Shell脚本教程
linux·运维·服务器·chrome·spring boot·学习·实施