FreeRTOS: 内存管理

发布时间:2026/10/5 18:11:56
FreeRTOS: 内存管理
FreeRTOS 内存管理架构图heap_4 内存分配流程图一、FreeRTOS 内存管理概述1.1 内存管理的重要性FreeRTOS 提供了多种内存管理方案heap_1 到 heap_5用于动态内存分配。内存管理特点·专门为嵌入式系统设计·支持多种分配策略·线程安全在调度器启动后·内存碎片优化1.2 五种内存管理方案方案特点适用场景heap_1最简单只分配不释放不需要释放内存的应用heap_2支持释放可能碎片不需要碎片优化的应用heap_3简单包装 malloc/free需要标准库的应用heap_4相邻空闲块合并需要碎片优化的应用heap_5支持多内存区域内存不连续的系统 要点为什么不用标准库 malloc/free标准库 malloc/free 在嵌入式系统中并不总是可用、占用宝贵代码空间、不是线程安全的、且执行时间不确定非确定性。FreeRTOS 因此把内存分配放在可移植层内核申请内存调用 pvPortMalloc()、释放调用 vPortFree()并提供了 heap_1 ~ heap_5 五种实现。图heap_1 ~ heap_5 五种内存管理方案对比通过修改FreeRTOSConfig.h中的宏定义选择核心配置如下// 选择堆管理方案仅需开启其中一个 // #define configUSE_HEAP_1 1 // 启用heap_1方案 // #define configUSE_HEAP_2 1 // 启用heap_2方案 #define configUSE_HEAP_3 1 // 启用heap_3方案 // #define configUSE_HEAP_4 1 // 启用heap_4方案 // #define configUSE_HEAP_5 1 // 启用heap_5方案 // 配置堆内存总大小heap_1/2/4/5适用单位字节 #define configTOTAL_HEAP_SIZE (10 * 1024) // 10KB堆内存5种堆方案互斥只能启用其中一种heap_3方案无需配置configTOTAL_HEAP_SIZE依赖系统malloc/free其余4种均需配置堆总大小。二、内存管理配置2.1 FreeRTOSConfig.h 配置// 堆大小字节 #define configTOTAL_HEAP_SIZE ((size_t)(40 * 1024)) // 40KB // 内存分配钩子函数 #define configUSE_MALLOC_FAILED_HOOK 1 // 应用程序任务分配钩子 #define configUSE_APPLICATION_TASK_TAG 02.2 选择内存管理方案在工程中添加对应的 heap_x.c 文件例如·使用 heap_4添加 heap_4.c·使用 heap_5添加 heap_5.c三、内存管理 API3.1 内存分配与释放void *pvPortMalloc(size_t xWantedSize); void vPortFree(void *pv); // 使用示例 void MallocFreeExample(void) { // 分配内存 uint8_t *pBuffer (uint8_t*)pvPortMalloc(128); if(pBuffer ! NULL) { // 使用内存 memset(pBuffer, 0, 128); // 释放内存 vPortFree(pBuffer); } else { printf(内存分配失败\r\n); } }3.2 堆信息查询// 获取剩余堆大小 size_t xPortGetFreeHeapSize(void); // 获取最小剩余堆大小历史最低 size_t xPortGetMinimumEverFreeHeapSize(void); // 使用示例 void PrintHeapInfo(void) { size_t freeHeap xPortGetFreeHeapSize(); size_t minEverHeap xPortGetMinimumEverFreeHeapSize(); printf(当前剩余堆: %u 字节\r\n, freeHeap); printf(历史最小剩余: %u 字节\r\n, minEverHeap); }四、heap_1 详解4.1 heap_1 特点·实现简单·只支持 pvPortMalloc()不支持 vPortFree()·无内存碎片·适用于不需要释放内存的系统4.2 heap_1 工作原理// heap_1 的内存布局 static uint8_t ucHeap[configTOTAL_HEAP_SIZE]; static size_t xNextFreeByte (size_t)0; void *pvPortMalloc(size_t xWantedSize) { void *pvReturn NULL; // 对齐处理 xWantedSize (xWantedSize (portBYTE_ALIGNMENT - 1)) ~(portBYTE_ALIGNMENT - 1); // 检查是否有足够空间 if(xNextFreeByte xWantedSize configTOTAL_HEAP_SIZE) { pvReturn ucHeap[xNextFreeByte]; xNextFreeByte xWantedSize; } return pvReturn; } void vPortFree(void *pv) { // heap_1 不支持释放 (void)pv; }4.3 heap_1 适用场景// ✅ 适用系统启动时分配永不释放 void InitSystem(void) { g_pBuffer pvPortMalloc(1024); // 系统运行期间一直使用不释放 }五、heap_2 详解5.1 heap_2 特点·支持 pvPortMalloc() 和 vPortFree()·使用最佳匹配算法·不合并相邻空闲块可能碎片·适合固定大小内存分配5.2 heap_2 工作原理使用链表管理空闲内存块typedef struct A_BLOCK_LINK { struct A_BLOCK_LINK *pxNextFreeBlock; size_t xBlockSize; } BlockLink_t; static uint8_t ucHeap[configTOTAL_HEAP_SIZE];5.3 heap_2 使用示例void Heap2Example(void) { // 分配多个固定大小块 void *p1 pvPortMalloc(64); void *p2 pvPortMalloc(64); void *p3 pvPortMalloc(64); // 释放中间的块 vPortFree(p2); // 再次分配64字节会重用p2的空间最佳匹配 void *p4 pvPortMalloc(64); }六、heap_3 详解6.1 heap_3 特点·简单包装标准库的 malloc() 和 free()·使用调度器挂起保证线程安全·堆大小由链接器脚本定义6.2 heap_3 实现void *pvPortMalloc(size_t xWantedSize) { void *pvReturn; vTaskSuspendAll(); pvReturn malloc(xWantedSize); xTaskResumeAll(); return pvReturn; } void vPortFree(void *pv) { if(pv ! NULL) { vTaskSuspendAll(); free(pv); xTaskResumeAll(); } }6.3 heap_3 注意事项// 注意堆大小不是由 configTOTAL_HEAP_SIZE 定义 // 而是由链接器脚本决定七、heap_4 详解推荐7.1 heap_4 特点·支持 pvPortMalloc() 和 vPortFree()·使用首次匹配算法·合并相邻空闲块减少碎片·适用于大多数应用7.2 heap_4 内存合并// 示例释放内存时合并相邻空闲块 void FreeAndCoalesceExample(void) { // 初始状态[已用1][已用2][已用3] void *p1 pvPortMalloc(100); void *p2 pvPortMalloc(100); void *p3 pvPortMalloc(100); // 释放 p2[已用1][空闲100][已用3] vPortFree(p2); // 释放 p1[空闲200][已用3]合并 vPortFree(p1); }7.3 heap_4 完整示例#include FreeRTOS.h // 确保使用 heap_4 // 在工程中添加 heap_4.c void Heap4Demo(void) { void *pBlock1, *pBlock2, *pBlock3; // 分配不同大小的块 pBlock1 pvPortMalloc(200); pBlock2 pvPortMalloc(300); pBlock3 pvPortMalloc(100); printf(初始剩余堆: %u\r\n, xPortGetFreeHeapSize()); // 释放中间的块 vPortFree(pBlock2); printf(释放 pBlock2 后剩余: %u\r\n, xPortGetFreeHeapSize()); // 释放第一个块会与前面的空闲块合并 vPortFree(pBlock1); printf(释放 pBlock1 后剩余: %u\r\n, xPortGetFreeHeapSize()); // 分配一个更大的块 void *pBlock4 pvPortMalloc(400); printf(分配 pBlock4 后剩余: %u\r\n, xPortGetFreeHeapSize()); }八、heap_5 详解8.1 heap_5 特点·支持多个不连续的内存区域·需要在使用前初始化·适用于内存分散的系统如部分在内部RAM部分在外部RAM8.2 heap_5 初始化// 内存区域描述结构 typedef struct HeapRegion { uint8_t *pucStartAddress; size_t xSizeInBytes; } HeapRegion_t; void vPortDefineHeapRegions(const HeapRegion_t *const pxHeapRegions); // 使用示例 void InitHeap5(void) { // 定义多个内存区域 const HeapRegion_t xHeapRegions[] { // 内部RAM64KB { (uint8_t*)0x20000000, 0x10000 }, // 外部RAM1MB { (uint8_t*)0x60000000, 0x100000 }, // 结束标记 { NULL, 0 } }; // 初始化 heap_5 vPortDefineHeapRegions(xHeapRegions); printf(heap_5 初始化完成\r\n); }8.3 heap_5 使用示例void Heap5Demo(void) { // 先初始化 InitHeap5(); // 分配内存会从最合适的区域分配 void *pSmallBlock pvPortMalloc(100); // 可能从内部RAM分配 void *pLargeBlock pvPortMalloc(50000); // 可能从外部RAM分配 // 使用后释放 vPortFree(pSmallBlock); vPortFree(pLargeBlock); }九、内存分配失败钩子9.1 实现钩子函数void vApplicationMallocFailedHook(void) { // 内存分配失败时调用 printf(内存分配失败剩余堆: %u\r\n, xPortGetFreeHeapSize()); // 可以记录错误、复位系统、进入安全状态 while(1) { // 停止在这里 } }9.2 启用钩子函数#define configUSE_MALLOC_FAILED_HOOK 1十、真实案例内存池实现10.1 案例背景使用固定大小内存池避免碎片提高性能。10.2 完整实现#include FreeRTOS.h #include semphr.h #define POOL_BLOCK_SIZE 64 #define POOL_BLOCK_COUNT 16 // 内存池 static uint8_t ucMemoryPool[POOL_BLOCK_COUNT][POOL_BLOCK_SIZE]; static uint8_t ucBlockUsed[POOL_BLOCK_COUNT]; static SemaphoreHandle_t xPoolMutex; // 初始化内存池 void InitMemoryPool(void) { xPoolMutex xSemaphoreCreateMutex(); for(int i 0; i POOL_BLOCK_COUNT; i) { ucBlockUsed[i] 0; } } // 从池中分配 void *PoolMalloc(void) { void *pBlock NULL; if(xSemaphoreTake(xPoolMutex, pdMS_TO_TICKS(100)) pdTRUE) { // 查找空闲块 for(int i 0; i POOL_BLOCK_COUNT; i) { if(ucBlockUsed[i] 0) { ucBlockUsed[i] 1; pBlock ucMemoryPool[i][0]; break; } } xSemaphoreGive(xPoolMutex); } return pBlock; } // 释放回池中 void PoolFree(void *pv) { if(xSemaphoreTake(xPoolMutex, pdMS_TO_TICKS(100)) pdTRUE) { // 查找对应的块 for(int i 0; i POOL_BLOCK_COUNT; i) { if(pv ucMemoryPool[i][0]) { ucBlockUsed[i] 0; break; } } xSemaphoreGive(xPoolMutex); } } // 获取池使用情况 void GetPoolUsage(void) { int usedCount 0; if(xSemaphoreTake(xPoolMutex, pdMS_TO_TICKS(100)) pdTRUE) { for(int i 0; i POOL_BLOCK_COUNT; i) { if(ucBlockUsed[i]) { usedCount; } } xSemaphoreGive(xPoolMutex); } printf(内存池使用: %d/%d\r\n, usedCount, POOL_BLOCK_COUNT); } // 使用示例 void MemoryPoolDemo(void) { InitMemoryPool(); // 分配多个块 void *p1 PoolMalloc(); void *p2 PoolMalloc(); void *p3 PoolMalloc(); GetPoolUsage(); // 3/16 // 释放 PoolFree(p2); GetPoolUsage(); // 2/16 }十一、真实案例内存监控系统11.1 案例背景监控内存使用情况记录分配/释放操作。11.2 完整实现#include FreeRTOS.h #include task.h #include #define MAX_ALLOCATIONS 50 typedef struct { void *pAddress; size_t size; const char *pFile; int line; uint32_t timestamp; } AllocationRecord_t; static AllocationRecord_t allocations[MAX_ALLOCATIONS]; static int allocationCount 0; static SemaphoreHandle_t xMonitorMutex; // 初始化监控 void InitMemoryMonitor(void) { xMonitorMutex xSemaphoreCreateMutex(); memset(allocations, 0, sizeof(allocations)); allocationCount 0; } // 带监控的内存分配 void *MallocMonitor(size_t size, const char *file, int line) { void *p pvPortMalloc(size); if(p ! NULL xSemaphoreTake(xMonitorMutex, 0) pdTRUE) { // 记录分配 if(allocationCount MAX_ALLOCATIONS) { allocations[allocationCount].pAddress p; allocations[allocationCount].size size; allocations[allocationCount].pFile file; allocations[allocationCount].line line; allocations[allocationCount].timestamp xTaskGetTickCount(); allocationCount; } xSemaphoreGive(xMonitorMutex); } return p; } // 带监控的内存释放 void FreeMonitor(void *p) { if(p ! NULL xSemaphoreTake(xMonitorMutex, 0) pdTRUE) { // 查找并删除记录 for(int i 0; i allocationCount; i) { if(allocations[i].pAddress p) { // 移动后面的记录 for(int j i; j allocationCount - 1; j) { allocations[j] allocations[j 1]; } allocationCount--; break; } } xSemaphoreGive(xMonitorMutex); } vPortFree(p); } // 打印内存分配信息 void PrintMemoryAllocationInfo(void) { if(xSemaphoreTake(xMonitorMutex, pdMS_TO_TICKS(100)) pdTRUE) { printf(当前内存分配 (%d):\r\n, allocationCount); for(int i 0; i allocationCount; i) { printf( #%d: %p, %u 字节, %s:%d, %lu ms\r\n, i, allocations[i].pAddress, allocations[i].size, allocations[i].pFile, allocations[i].line, allocations[i].timestamp); } xSemaphoreGive(xMonitorMutex); } printf(剩余堆: %u 字节\r\n, xPortGetFreeHeapSize()); printf(历史最小剩余: %u 字节\r\n, xPortGetMinimumEverFreeHeapSize()); } // 宏定义方便使用 #define MONITOR_MALLOC(size) MallocMonitor(size, __FILE__, __LINE__) #define MONITOR_FREE(p) FreeMonitor(p) // 使用示例 void MemoryMonitorDemo(void) { InitMemoryMonitor(); // 使用带监控的分配/释放 void *p1 MONITOR_MALLOC(100); void *p2 MONITOR_MALLOC(200); PrintMemoryAllocationInfo(); MONITOR_FREE(p1); PrintMemoryAllocationInfo(); }十二、内存管理最佳实践12.1 选择合适的 heap 方案// ✅ 大多数应用使用 heap_4// 自动合并空闲块减少碎片// ✅ 多内存区域使用 heap_5// 支持不连续的内存区域// ✅ 简单应用永不释放使用 heap_1// 最简单无碎片// ❌ 除非必要避免 heap_2// 不合并空闲块碎片问题12.2 避免内存碎片// ✅ 好的做法固定大小分配 for(int i 0; i 10; i) { void *p pvPortMalloc(64); // 使用 vPortFree(p); // 碎片少 } // ❌ 不好的做法大小变化大 void *p1 pvPortMalloc(1000); void *p2 pvPortMalloc(100); void *p3 pvPortMalloc(500); vPortFree(p1); vPortFree(p3); // p2 卡在中间碎片严重12.3 检查返回值// ✅ 总是检查返回值 void *p pvPortMalloc(size); if(p ! NULL) { // 使用内存 } else { // 处理错误 }12.4 及时释放内存// ✅ 函数内部分配及时释放 void Function(void) { void *p pvPortMalloc(100); if(p ! NULL) { // 使用 vPortFree(p); // 释放 } } 要点选型要点同一时间只能链接一个 heap_x.c 源文件位于 Source/portable/MemMang/heap_1/2/4/5 的堆大小由 FreeRTOSConfig.h 的 configTOTAL_HEAP_SIZE 决定heap_3 使用链接器分配的堆可用 xPortGetFreeHeapSize() 与 xPortGetMinimumEverFreeHeapSize() 监控堆余量辅助优化堆大小。