这一节来看一个平时很容易被忽略的问题:
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
这一点一定不要混淆。