FreeRTOS的堆管理
上文對FreeRTOs的目錄結構進行了說明,其中提到了FreeRTOS\Source\portable\MemMang目錄下的五個heap_n.c文件,本文將對這個五個文件的作用、差異、使用場景進行對比,以便選擇出適合自己項目的堆管理模式。
- FreeRTOS使用pvPortMalloc()來分配內存。
- vPortFree()來釋放內存。
Heap_1.c
主要用於小型專一嵌入式系統。內核在任何實時任務執行之前先分配內存,一次分配永久使用並不再改變,可靠性較高。
堆的總容量 configTOTAL_HEAP_SIZE 在 FreeRTOSConfig.h 文件中配置
每創建一個任務都會分配一個堆控制塊(TCB:Task control block)和一個棧(Stack)
- A:代表整個可分配空間
- B:當一個任務被創建出來
- C:當三個任務被創建出來
Heap_2.c
- Heap_2 保留的主要目的是向后兼容,不推薦在新項目中使用。可使用Heap_4作為替代。
- Heap_2 采用最佳適配算法,適用於需要頻繁創建和刪除需要分配固定棧內存的任務。
Heap_3.c
- 在heap3.c中 configTOTAL_HEAP_SIZE的配置將不再生效。
- Heap_3通過暫時掛起FreeRTOS的調度來實現malloc()和free()的線程安全。(待補充)
Heap_4.c
- Heap_4采用首次適應算法來分配內存。heap4將相鄰未分配的內存結合成為整個大內存來減少碎片內存。
Heap5.c
- heap_5和heap_4的使用完全一致。
- heap_5可以對任意位置的空間進行分配,
- heap_5在使用之前需要通過vPortDefineHeapRegions()函數進行初始化,之后才可以使用pvPortMalloc()進行內存分配。
- PortDefineHeapRegions()的作用是明確每個分散空間的初始位置和大小。
- 原型描述:
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
- 返回值結構
- 原型描述:
typedef struct HeapRegion
{
/* 內存塊的起始地址將成為堆的一部分.*/
uint8_t *pucStartAddress;
/* 堆的容量大小bytes. */
size_t xSizeInBytes;
} HeapRegion_t;
下圖表示vPortDefineHeapRegions函數的具體使用場景RAM1,RAM2,RAM3分別代表三個空閑空間
/* 圖最左側堆:A 定以RAM1-3的基本信息. */
#define RAM1_START_ADDRESS ( ( uint8_t * ) 0x00010000 )
#define RAM1_SIZE ( 65 * 1024 )
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
const HeapRegion_t xHeapRegions[] =
{
{ RAM1_START_ADDRESS, RAM1_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
{ NULL, 0 } /* 標志數組的結尾. */
};
int main( void )
{
/* 初始化heap_5 */
vPortDefineHeapRegions( xHeapRegions );
/* 編碼區域。*/
}
-
圖A僅僅展示了RAM結構,圖B 包含了堆分配的一些細節
-
由於RAM的管理需要鏈接腳本,圖B RAM1包含了鏈接腳本,RAM2,和RAM3為空。RAM1被分為兩個區域,0x10000-0x01nnnn用來存放連接腳本,只有0x01nnnn-0x01FFFF可用,即heap5的可用空間為0x01nnnn-0x01FFFF,RAM2,RAM3。此時如果起始位置依然以0x010000作為起點將覆蓋存放變量的內存,所以必須從0x0001nnnn作為起點,可以在HeapRegion_t結構中使用xHeapRegions[] 數組作為起始地址,但由於起始地址難判定,后續結構的必要更新,堆重疊的問題此方案並不推薦。
-
完美推薦方案C
* 定以沒有被鏈接器使用的兩個起始地址和容量 */
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
/* 定義一個數組為heap_5使用的一部分,此數組將會被鏈接器放置於RAM1 */
#define RAM1_HEAP_SIZE ( 30 * 1024 )
static uint8_t ucHeap[ RAM1_HEAP_SIZE ];
/* 定義一個數組HeapRegion_t,第一個入口只定義了ucHeap數組。像之前一樣HeapRegion_t結構定以仍需地址從小到大排列。*/
const HeapRegion_t xHeapRegions[] =
{
{ ucHeap, RAM1_HEAP_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
{ NULL, 0 } /* 標志數組的結束. */
};
優勢
- 初始地址不再是常量
- 鏈接器自動設置HeapRegion_t結構
- 內存分配給heap_5的數據不會被鏈接器覆蓋
- 如果ucHeap太大應用將不會鏈接
堆分配相關函數
size_t xPortGetFreeHeapSize( void );
當被調用時返回堆中可用字節,可用於優化堆大小。當時用heap_3分配方案時此函數不生效。size_t xPortGetMinimumEverFreeHeapSize( void );
當調用時返回FreeRTOS應自開始運行從未存在於堆中的最小未分配字節。 可用於了解是否存在堆溢出情況。 **只可用於heap_4,heap_5堆分配方案中。void vApplicationMallocFailedHook( void );
內存分配的情況無處不在,可在應用中直接調用,此外在freeRTOS創建任務,隊列,信號量等操作時也會調用pvPortMalloc()函數。當pvPortMalloc()未找到符合大小的RAM空間時返回NULL,此時可調用回調函數vApplicationMallocFailedHook。- 當在FreeRTOSConfig.h配置了configUSE_MALLOC_FAILED_HOOK為1 則表示內存分配出錯時必須執行vApplicationMallocFailedHook函數。