FreeRTOS --(2)內存管理 heap1

目錄

1、內存大小

2、對齊

3、分配內存

4、小結


 

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 內存的接口,也就是說,這套內存管理是隻提供的分配,一旦申請成功後,這塊內存再也不能被釋放;實際上,大多數的嵌入式系統並不需要動態刪除任務、信號量、隊列等,而是在初始化的時候一次性創建好,便一直使用,永遠不用刪除。所以這個內存管理策略實現簡潔、安全可靠,使用的非常廣泛。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章