存管理)
FreeRTOS 內(nèi)存管理架構圖heap_4 內(nèi)存分配流程圖一、FreeRTOS 內(nèi)存管理概述1.1 內(nèi)存管理的重要性FreeRTOS 提供了多種內(nèi)存管理方案heap_1 到 heap_5用于動態(tài)內(nèi)存分配。內(nèi)存管理特點·專門為嵌入式系統(tǒng)設計·支持多種分配策略·線程安全在調(diào)度器啟動后·內(nèi)存碎片優(yōu)化1.2 五種內(nèi)存管理方案方案特點適用場景heap_1最簡單只分配不釋放不需要釋放內(nèi)存的應用heap_2支持釋放可能碎片不需要碎片優(yōu)化的應用heap_3簡單包裝 malloc/free需要標準庫的應用heap_4相鄰空閑塊合并需要碎片優(yōu)化的應用heap_5支持多內(nèi)存區(qū)域內(nèi)存不連續(xù)的系統(tǒng) 要點為什么不用標準庫 malloc/free標準庫 malloc/free 在嵌入式系統(tǒng)中并不總是可用、占用寶貴代碼空間、不是線程安全的、且執(zhí)行時間不確定非確定性。FreeRTOS 因此把內(nèi)存分配放在可移植層內(nèi)核申請內(nèi)存調(diào)用 pvPortMalloc()、釋放調(diào)用 vPortFree()并提供了 heap_1 ~ heap_5 五種實現(xiàn)。圖heap_1 ~ heap_5 五種內(nèi)存管理方案對比通過修改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方案 // 配置堆內(nèi)存總大小heap_1/2/4/5適用單位字節(jié) #define configTOTAL_HEAP_SIZE (10 * 1024) // 10KB堆內(nèi)存5種堆方案互斥只能啟用其中一種heap_3方案無需配置configTOTAL_HEAP_SIZE依賴系統(tǒng)malloc/free其余4種均需配置堆總大小。二、內(nèi)存管理配置2.1 FreeRTOSConfig.h 配置// 堆大小字節(jié) #define configTOTAL_HEAP_SIZE ((size_t)(40 * 1024)) // 40KB // 內(nèi)存分配鉤子函數(shù) #define configUSE_MALLOC_FAILED_HOOK 1 // 應用程序任務分配鉤子 #define configUSE_APPLICATION_TASK_TAG 02.2 選擇內(nèi)存管理方案在工程中添加對應的 heap_x.c 文件例如·使用 heap_4添加 heap_4.c·使用 heap_5添加 heap_5.c三、內(nèi)存管理 API3.1 內(nèi)存分配與釋放void *pvPortMalloc(size_t xWantedSize); void vPortFree(void *pv); // 使用示例 void MallocFreeExample(void) { // 分配內(nèi)存 uint8_t *pBuffer (uint8_t*)pvPortMalloc(128); if(pBuffer ! NULL) { // 使用內(nèi)存 memset(pBuffer, 0, 128); // 釋放內(nèi)存 vPortFree(pBuffer); } else { printf(內(nèi)存分配失敗\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 字節(jié)\r\n, freeHeap); printf(歷史最小剩余: %u 字節(jié)\r\n, minEverHeap); }四、heap_1 詳解4.1 heap_1 特點·實現(xiàn)簡單·只支持 pvPortMalloc()不支持 vPortFree()·無內(nèi)存碎片·適用于不需要釋放內(nèi)存的系統(tǒng)4.2 heap_1 工作原理// heap_1 的內(nèi)存布局 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 適用場景// ? 適用系統(tǒng)啟動時分配永不釋放 void InitSystem(void) { g_pBuffer pvPortMalloc(1024); // 系統(tǒng)運行期間一直使用不釋放 }五、heap_2 詳解5.1 heap_2 特點·支持 pvPortMalloc() 和 vPortFree()·使用最佳匹配算法·不合并相鄰空閑塊可能碎片·適合固定大小內(nèi)存分配5.2 heap_2 工作原理使用鏈表管理空閑內(nèi)存塊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字節(jié)會重用p2的空間最佳匹配 void *p4 pvPortMalloc(64); }六、heap_3 詳解6.1 heap_3 特點·簡單包裝標準庫的 malloc() 和 free()·使用調(diào)度器掛起保證線程安全·堆大小由鏈接器腳本定義6.2 heap_3 實現(xiàn)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()·使用首次匹配算法·合并相鄰空閑塊減少碎片·適用于大多數(shù)應用7.2 heap_4 內(nèi)存合并// 示例釋放內(nèi)存時合并相鄰空閑塊 void FreeAndCoalesceExample(void) { // 初始狀態(tài)[已用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 特點·支持多個不連續(xù)的內(nèi)存區(qū)域·需要在使用前初始化·適用于內(nèi)存分散的系統(tǒng)如部分在內(nèi)部RAM部分在外部RAM8.2 heap_5 初始化// 內(nèi)存區(qū)域描述結(jié)構 typedef struct HeapRegion { uint8_t *pucStartAddress; size_t xSizeInBytes; } HeapRegion_t; void vPortDefineHeapRegions(const HeapRegion_t *const pxHeapRegions); // 使用示例 void InitHeap5(void) { // 定義多個內(nèi)存區(qū)域 const HeapRegion_t xHeapRegions[] { // 內(nèi)部RAM64KB { (uint8_t*)0x20000000, 0x10000 }, // 外部RAM1MB { (uint8_t*)0x60000000, 0x100000 }, // 結(jié)束標記 { NULL, 0 } }; // 初始化 heap_5 vPortDefineHeapRegions(xHeapRegions); printf(heap_5 初始化完成\r\n); }8.3 heap_5 使用示例void Heap5Demo(void) { // 先初始化 InitHeap5(); // 分配內(nèi)存會從最合適的區(qū)域分配 void *pSmallBlock pvPortMalloc(100); // 可能從內(nèi)部RAM分配 void *pLargeBlock pvPortMalloc(50000); // 可能從外部RAM分配 // 使用后釋放 vPortFree(pSmallBlock); vPortFree(pLargeBlock); }九、內(nèi)存分配失敗鉤子9.1 實現(xiàn)鉤子函數(shù)void vApplicationMallocFailedHook(void) { // 內(nèi)存分配失敗時調(diào)用 printf(內(nèi)存分配失敗剩余堆: %u\r\n, xPortGetFreeHeapSize()); // 可以記錄錯誤、復位系統(tǒng)、進入安全狀態(tài) while(1) { // 停止在這里 } }9.2 啟用鉤子函數(shù)#define configUSE_MALLOC_FAILED_HOOK 1十、真實案例內(nèi)存池實現(xiàn)10.1 案例背景使用固定大小內(nèi)存池避免碎片提高性能。10.2 完整實現(xiàn)#include FreeRTOS.h #include semphr.h #define POOL_BLOCK_SIZE 64 #define POOL_BLOCK_COUNT 16 // 內(nèi)存池 static uint8_t ucMemoryPool[POOL_BLOCK_COUNT][POOL_BLOCK_SIZE]; static uint8_t ucBlockUsed[POOL_BLOCK_COUNT]; static SemaphoreHandle_t xPoolMutex; // 初始化內(nèi)存池 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(內(nèi)存池使用: %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 }十一、真實案例內(nèi)存監(jiān)控系統(tǒng)11.1 案例背景監(jiān)控內(nèi)存使用情況記錄分配/釋放操作。11.2 完整實現(xiàn)#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; // 初始化監(jiān)控 void InitMemoryMonitor(void) { xMonitorMutex xSemaphoreCreateMutex(); memset(allocations, 0, sizeof(allocations)); allocationCount 0; } // 帶監(jiān)控的內(nèi)存分配 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; } // 帶監(jiān)控的內(nèi)存釋放 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); } // 打印內(nèi)存分配信息 void PrintMemoryAllocationInfo(void) { if(xSemaphoreTake(xMonitorMutex, pdMS_TO_TICKS(100)) pdTRUE) { printf(當前內(nèi)存分配 (%d):\r\n, allocationCount); for(int i 0; i allocationCount; i) { printf( #%d: %p, %u 字節(jié), %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 字節(jié)\r\n, xPortGetFreeHeapSize()); printf(歷史最小剩余: %u 字節(jié)\r\n, xPortGetMinimumEverFreeHeapSize()); } // 宏定義方便使用 #define MONITOR_MALLOC(size) MallocMonitor(size, __FILE__, __LINE__) #define MONITOR_FREE(p) FreeMonitor(p) // 使用示例 void MemoryMonitorDemo(void) { InitMemoryMonitor(); // 使用帶監(jiān)控的分配/釋放 void *p1 MONITOR_MALLOC(100); void *p2 MONITOR_MALLOC(200); PrintMemoryAllocationInfo(); MONITOR_FREE(p1); PrintMemoryAllocationInfo(); }十二、內(nèi)存管理最佳實踐12.1 選擇合適的 heap 方案// ? 大多數(shù)應用使用 heap_4// 自動合并空閑塊減少碎片// ? 多內(nèi)存區(qū)域使用 heap_5// 支持不連續(xù)的內(nèi)存區(qū)域// ? 簡單應用永不釋放使用 heap_1// 最簡單無碎片// ? 除非必要避免 heap_2// 不合并空閑塊碎片問題12.2 避免內(nèi)存碎片// ? 好的做法固定大小分配 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) { // 使用內(nèi)存 } else { // 處理錯誤 }12.4 及時釋放內(nèi)存// ? 函數(shù)內(nèi)部分配及時釋放 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() 監(jiān)控堆余量輔助優(yōu)化堆大小。