1. FreeRTOS内存管理简介
1.1 核心功能与设计目标
• FreeRTOS的内存管理模块是操作系统的核心组件,专为嵌入式实时系统优化,提供以下核心能力:
• 动态内存分配:允许任务在运行时按需申请内存(如创建任务、队列、信号量等)。
• 静态内存分配:编译时预分配固定内存区域,避免运行时开销(适用于资源受限设备)。
• 碎片控制:通过合并相邻空闲内存块降低碎片风险。
1.2 为何不使用C库的malloc/free?
• 尽管C库函数通用,但在嵌入式场景中存在致命缺陷:
1. 线程不安全:标准库未考虑多任务竞争,可能引发数据错乱(如两个任务同时申请内存)。
2. 确定性差:执行时间不可预测,影响实时性。
3. 内存碎片:频繁分配释放导致内存被分割成小块,最终无法分配大块内存(例如连续申请不同大小的任务栈)。
4. 代码臃肿:标准库实现复杂,占用过多Flash空间(例如某些C库malloc代码量超过10KB)。
5. 硬件适配差:无法针对特定硬件优化(如STM32的CCM高速内存区)。
2. FreeRTOS内存管理算法
2.1 五大算法对比
| 算法 | 优点 | 缺点 |
|
heap_1
|
分配简单,时间确定
|
只允许申请内存,不允许释放内存
|
|
heap_2
|
允许申请和释放内存
|
不能合并相邻的空闲内存块会产生碎片、时间不定
|
|
heap_3
|
直接调用C库函数malloc()和 free() ,简单
|
速度慢、时间不定
|
|
heap_4
|
相邻空闲内存可合并,减少内存碎片的产生
|
时间不定
|
|
heap_5
|
能够管理多个非连续内存区域的heap_4
|
时间不定
|
2.2 FreeRTOS内存管理算法的深度解析
1. Heap_1:单次分配,永不回收。
• 如图

• 核心机制:
• heap_1是FreeRTOS最简单的内存管理算法,通过预定义的静态数组 ucHeap[configTOTAL_HEAP_SIZE] 实现线性分配。所有内存请求按顺序从数组起始地址递增分配,不支持内存释放,仅适用于初始化阶段创建对象后永不删除的场景。
• 实现细节:
• 内存池初始化:在首次调用 pvPortMalloc() 时,根据 portBYTE_ALIGNMENT 对齐数组首地址,确保分配的内存满足处理器对齐要求(如STM32需8字节对齐)。
• 分配过程:记录当前分配位置 xNextFreeByte ,每次分配后直接递增指针。若剩余空间不足,返回 NULL并触发 vApplicationMallocFailedHook() 钩子函数。
• 局限性
• 内存利用率低,无法回收已分配内存,长期运行可能导致内存耗尽。
2. Heap_2:使用最佳匹配算法,但内存碎片化严重。
• 如图:

• 核心机制:
•heap_2在Heap_1基础上增加了内存释放功能,采用最佳匹配算法(Best Fit)。空闲块通过链表管理,分配时选择最小且足够的空闲块,但不合并相邻空闲块,导致外部碎片积累。
• 实现细节:
• 空闲块链表:使用BlockLink_t 结构维护空闲块,包含指向下一块的指针和块大小。
• 分配策略:遍历链表找到最接近请求大小的块,分割剩余空间为新空闲块(若剩余空间足够)。
• 释放策略:仅将释放块标记为空闲,不进行合并。
• 局限性
• 长期运行后碎片化严重,无法分配连续大内存。
3. Heap_3:封装C库,兼容但低效
• 核心机制
• heap_3直接调用标准C库的 malloc() 和 free() ,但通过挂起调度器实现线程安全。内存堆大小由链接器配置(如STM32的启动文件定义),与 configTOTAL_HEAP_SIZE 无关。
• 实现细节
• 线程安全:在 pvPortMalloc() 和 vPortFree() 中调用 vTaskSuspendAll() 暂停任务调度。
• 内存来源:依赖编译器的堆空间,可能分散在多个内存区域(如内部SRAM和外部SDRAM)。
• 局限性
• 非确定性,分配/释放时间不可预测。
• 标准库的碎片问题未解决,且代码体积大(如某些C库实现占用超10KB Flash)。
4. Heap_4:使用首次适应算法,也对碎片进行优化
• 如图

• 核心机制
• heap_4采用首次适应算法(First Fit)和空闲块合并机制。分配时从链表头部查找第一个足够大的块,释放时检查相邻块是否空闲并合并,显著减少碎片。
• 实现细节
• 空闲块管理:链表按地址升序排列,释放时触发 prvInsertBlockIntoFreeList() 合并相邻块。
• 内存标记:通过块头部的 xBlockSize 最高位标记是否空闲(0为空闲,1为已分配)。
• 性能优化:支持动态调整空闲链表,减少遍历时间。
• 局限性
• 内存必须连续,无法管理非连续物理内存(如多块SRAM)。
5. Heap_5:多区域管理的Heap_4增强版 。
• 核心机制
• heap_5在Heap_4基础上扩展支持多块非连续内存区域。用户需预先定义HeapRegion_t 数组描述各内存块起始地址和大小,初始化时通过vPortDefineHeapRegions() 将其串联为逻辑连续空间。
• 实现细节
• 多区域配置:
const HeapRegion_t xHeapRegions[] = {
{ (uint8_t*)0x20000000, 0x10000 }, // 内部RAM 64KB
{ (uint8_t*)0x60000000, 0x80000 }, // 外部SDRAM 512KB
{ NULL, 0 } // 结束标记
};
vPortDefineHeapRegions(xHeapRegions);
• 分配策略:与Heap_4相同,但支持跨区域分配。
• 局限性
• 初始化复杂,需手动定义内存区域。
• 合并算法跨区域效率较低。
• 总结与选型建议
• 确定性优先:选择Heap_1(医疗设备、航天控制)。
• 低碎片需求:首选Heap_4(通用IoT设备)。
• 非连续内存:必须使用Heap_5(多核/大内存系统)。
• 兼容性要求:考虑Heap_3(混合开发环境)。
3. FreeRTOS内存管理的函数
3.1 内存申请
void *pvPortMalloc(size_t xWantedSize);
• 参数:xWantedSize为请求字节数(自动对齐,如8字节对齐)。
• 返回值:成功返回内存首地址,失败返回NULL(可挂钩configUSE_MALLOC_FAILED_HOOK处理)
• 示例:创建任务时自动调用此函数分配TCB和栈空间。
3.2 内存释放
void vPortFree(void *pv);
• 参数: pv 为 pvPortMalloc 返回的地址
• 注意:未释放内存会导致泄漏,需配合 xPortGetFreeHeapSize 监控
3.3 堆状态查询
size_t xPortGetFreeHeapSize(void); // 当前空闲内存
size_t xPortGetMinimumEverFreeHeapSize(void); // 历史最小空闲值
4. 实验
1. 动态创建两个任务:task1、task2,两个任务作用如下:
• task1:检测按键;
• task2:打印剩余空闲内存大小;
2. 检测到按键1按下,申请内存;检测到按键2按下,释放内存。
#include "FreeRTOS.h"
#include "task.h"
#include "led.h"
#include "freertos_test.h"
#include "key.h"
#include "uart1.h"
TaskHandle_t task1_handle; /* 任务句柄 */
TaskHandle_t task2_handle; /* 任务句柄 */
void task1(void *pvParameters)
{
new_printf("task1创建成功,准备运行...\r\n");
uint8_t key_num = 0;
uint8_t *buf = NULL;
while(1)
{
key_num = key_scan();
if(key_num == 1){
buf = pvPortMalloc(30);
if(buf != NULL){
new_printf("内存申请成功\r\n");
}else {
new_printf("内存申请失败\r\n");
}
}else if(key_num == 2){
if(buf != NULL){
vPortFree(buf);
new_printf("内存释放成功\r\n");
buf = NULL;
}else {
new_printf("内存释放失败\r\n");
}
}
vTaskDelay(10);
}
}
void task2(void *pvParameters)
{
new_printf("task2创建成功,准备运行...\r\n");
while(1)
{
new_printf("剩余空闲内存是:%d\r\n",xPortGetFreeHeapSize());
vTaskDelay(1000);
}
}
void freertos_test(void)
{
xTaskCreate(task1, /* 任务函数 */
"task1", /* 任务名称 */
128, /* 任务堆栈大小 */
NULL, /* 传入给任务函数的参数 */
1, /* 任务优先级 */
&task1_handle); /* 任务句柄 */
xTaskCreate(task2, /* 任务函数 */
"task2", /* 任务名称 */
128, /* 任务堆栈大小 */
NULL, /* 传入给任务函数的参数 */
1, /* 任务优先级 */
&task2_handle); /* 任务句柄 */
}
3028

被折叠的 条评论
为什么被折叠?



