目錄
FreeRTOS 提供了5種內存堆管理方案,分別對應heap1/heap2/heap3/heap4/heap5,提供內存管理是作爲 OS 的一項基本功能,FreeRTOS 根據具體的使用場景,將內存管理按需切分成爲了 5 部分,以供不同的場景來針對性使用;
其實庫函數的 malloc 和 free 已經是提供了內存的動態管理功能,但是呢介於一下幾個原因:
- 在嵌入式系統中,它們並不總是可以使用的;
- 它們會佔用更多寶貴的代碼空間;
- 它們沒有線程保護;
- 它們不具有確定性(每次調用執行的時間可能會不同);
在小型實時嵌入式 OS 中,使用 malloc 和 free,並不是最明智的選擇;所以,FreeRTOS 使用了:pvPortMallo() 和 vPortFree() 函數來代替 malloc() 和 free() 函數,來進行內存管理;
FreeRTOS 內存管理相關的 SourceCode 放置在:
FreeRTOS\Source\portable\MemMang
一共 5 個,今天就來看看第一種內存管理 heap1 是如何實現的;
1、內存大小
heap1 中有幾個關鍵的宏定義:
configTOTAL_HEAP_SIZE
這個是需要用戶根據自己的芯片具體情況來定義的,FreeRTOS 管理的內存就來自於這個值:
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
ucHeap 就是管理的對象;
2、對齊
有的處理器是對內存對齊有要求的,比如 ARM-CM3 等,AAPCS規則要求堆棧保持8字節對齊。給任務分配棧時需要保證棧是8字節對齊的。所以這裏 FreeRTOS 就需要涉及到對齊操作;針對 ARM-CM3 這類處理器來說,在portmacro.h 文件中,定義了對齊的字節數:
/* Hardware specifics. */
#define portBYTE_ALIGNMENT 8
而在 portable.h 中,定義了對應的 Mask(8字節對齊,那麼都要是 8 的倍數,也就是二進制的 4'b1000,所以 MASK 是 4'b0111 也就是 0x07):
#if portBYTE_ALIGNMENT == 8
#define portBYTE_ALIGNMENT_MASK ( 0x0007 )
#endif
3、分配內存
分配內存使用了 pvPortMalloc 函數,入參是希望分配的 Size,返回值是分配到內存的起始地址,失敗的話返回 NULL:
/* Index into the ucHeap array. */
static size_t xNextFreeByte = ( size_t ) 0;
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;
/* Ensure that blocks are always aligned to the required number of bytes. */
#if( portBYTE_ALIGNMENT != 1 )
{
if( xWantedSize & portBYTE_ALIGNMENT_MASK )
{
/* Byte alignment required. */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
}
#endif
vTaskSuspendAll();
{
if( pucAlignedHeap == NULL )
{
/* Ensure the heap starts on a correctly aligned boundary. */
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
}
/* Check there is enough room left for the allocation. */
if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )/* Check for overflow. */
{
/* Return the next free byte then increment the index past this
block. */
pvReturn = pucAlignedHeap + xNextFreeByte;
xNextFreeByte += xWantedSize;
}
traceMALLOC( pvReturn, xWantedSize );
}
( void ) xTaskResumeAll();
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
pvReturn 就是如果被分配到內存後的起始地址,初始化成爲 NULL;
這裏注意一下,pucAlignedHeap 是一個 static 變量,它記錄了對齊後,內存 heap 的起始地址;
如果 portBYTE_ALIGNMENT != 1,也就是使用了對齊,那麼需要按需來調整分配內存的大小,也就是 xWantedSize,使其能夠達到對齊的效果;
接着調用 vTaskSuspendAll();,暫時關閉 OS 調度;
接着對初始的 ucHeap[ configTOTAL_HEAP_SIZE ] 進行對齊操作:
/* Ensure the heap starts on a correctly aligned boundary. */
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
這裏爲什麼是 ucHeap[ portBYTE_ALIGNMENT ] 呢,因爲如果 ucHeap 的起始地址本就不對齊的話,那麼經過對齊操作後得到的地址,便可能小於真實的 ucHeap,所以這裏往前挪了一點;
接下來便是檢查是否能夠正常分配到想要的 Size 的內存,xNextFreeByte 記錄了不斷的分配內存後,可用於分配內存在 ucHeap 的起始地址;這裏的 configADJUSTED_HEAP_SIZE 很關鍵,它被定義爲:
/* A few bytes might be lost to byte aligning the heap start address. */
#define configADJUSTED_HEAP_SIZE ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
也就是在 configTOTAL_HEAP_SIZE 基礎之上預留出了 portBYTE_ALIGNMENT 的空間,這個就是用於了剛剛上一步說的 ucHeap[ portBYTE_ALIGNMENT ] 這部分空間;
如果分配成功,那麼 pvReturn 被更新爲 ucHeap 對齊後的起始地址,加上可用內存的起始地址指針 xNextFreeByte;
同時,更新 xNextFreeByte 指針;
整個過程,保證了 ucHeap 的起始地址對齊到 pucAlignedHeap,分配內存的時候,Size 是對齊的;
然後再調用 xTaskResumeAll(); 來恢復 OS 調度;
當然,如果定義了 configUSE_MALLOC_FAILED_HOOK,在分配失敗的時候,會去調用 vApplicationMallocFailedHook 回調;
4、小結
heap1 不提供 Free 內存的接口,也就是說,這套內存管理是隻提供的分配,一旦申請成功後,這塊內存再也不能被釋放;實際上,大多數的嵌入式系統並不需要動態刪除任務、信號量、隊列等,而是在初始化的時候一次性創建好,便一直使用,永遠不用刪除。所以這個內存管理策略實現簡潔、安全可靠,使用的非常廣泛。