拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FreeRTOS: 内存管理

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 0

2.2 选择内存管理方案

在工程中添加对应的 heap_x.c 文件,例如:

·使用 heap_4:添加 heap_4.c

·使用 heap_5:添加 heap_5.c

三、内存管理 API

3.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,部分在外部RAM)

8.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[] = { // 内部RAM(64KB) { (uint8_t*)0x20000000, 0x10000 }, // 外部RAM(1MB) { (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() 监控堆余量,辅助优化堆大小。

返回列表