1. 程式人生 > >freertos之heap淺析

freertos之heap淺析

前言

這幾天看原始碼,先看了queue,task東西有點多,還是先以少到多,慢慢來。
在freertos中,官方實現了5種方法來分配對記憶體,沒有直接使用malloc 和 free函式,因為執行緒不安全,換句話說就是在malloc的時候可能被搶佔。下面來介紹五個檔案實現的堆記憶體分配和釋放的方法。


heap1.c

heap1.c這是最簡單的實現。它不允許在分配記憶體之後釋放記憶體。儘管這樣。heap_1.c適用於大量的嵌入式應用程式。這是因為許多小而深入的應用程式在系統啟動時建立所有所需的任務、佇列、訊號量等,然後在程式的生命週期中使用所有這些物件(直到應用程式再次關閉或重新啟動)。任何東西都不會被刪除,實現只是根據RAM的請求將單個數組細分為更小的塊。陣列的總大小(堆的總大小)是由configTOTAL堆大小設定的,這個大小在FreeRTOSConfig.h中定義。
heap_1的實現:

  • 可以使用如果您的應用程式沒有刪除任務,佇列,訊號量,互斥鎖,等等。(實際上涵蓋了大多數應用程式FreeRTOS被使用)。
  • 總是確定性的(總是需要相同的執行時間),並且不會導致記憶體碎片。
  • 是非常簡單的,從靜態分配的陣列中分配記憶體,這意味著它通常適用於不允許真正的動態記憶體分配的應用程式。
void *pvPortMalloc( size_t xWantedSize ){
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;
	/* 保證申請的記憶體塊是以 portBYTE_ALIGNMENT 對齊的,如果本身就是1位元組對齊那就不做操作了*/
	#if( portBYTE_ALIGNMENT != 1 ){
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )
		{
			/* Byte alignment required. */
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif

	vTaskSuspendAll();/* 就是給排程器上鎖,所有任務不得排程 */
	{
		if( pucAlignedHeap == NULL )/* 第一次malloc分配 */
		{
			/* Ensure the heap starts on a correctly aligned boundary. */
			/* 保證堆區域是在正確的地址上開始的 */
			pucAlignedHeap = ( uint8_t * ) (   ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) 
										& ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
		}
		/* 保證有足夠容量 和 檢查xWantedSize 是否為負數或者 ( xNextFreeByte + xWantedSize )溢位*/
		if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
			( ( xNextFreeByte + xWantedSize ) > xNextFreeByte )	){
			/* Return the next free byte then increment the index past this block. */
			pvReturn = pucAlignedHeap + xNextFreeByte;
			xNextFreeByte += xWantedSize;
		}
		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();/* 就是給排程器解鎖,所有任務開始重新排程 */
//其實還有個記憶體分配失敗的預定義函式,如果記憶體分配失敗可以在這個函式中做相應的操作
	return pvReturn;
}
/*-----------------------------------------------------------*/
void vPortFree( void *pv )
{
	/* 這種模式分配的堆記憶體不可以被釋放,可以用heap2.c heap3.c heap4.c等代替 */
	( void ) pv;
	configASSERT( pv == NULL );
}
/*-----------------------------------------------------------*/
void vPortInitialiseBlocks( void )
{
	/* 靜態記憶體初始化 xNextFreeByte 就是已經用了的記憶體 */
	xNextFreeByte = ( size_t ) 0;
}
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void )
{
	/* configADJUSTED_HEAP_SIZE 是使用者自定義的系統中總記憶體    xNextFreeByte 就是已經用了的記憶體 */
	return ( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}

heap2.c

該方案使用了最佳擬合算法,並且與heap1.c不同,它允許釋放以前分配的塊。它不會將相鄰的自由塊合併成單個大塊,但是heap_4.c實現了合併相鄰的空閒記憶體塊。
xPortGetFreeHeapSize() API函式返回仍然未分配的堆空間的總量(允許對configTOTAL_HEAP_SIZE設定進行優化),但沒有提供關於如何將未分配的記憶體分割成更小塊的資訊。
heap2.c實現:

  • 當應用程式重複刪除任務、佇列、訊號量、互斥物件等時,也可以使用此方法,但關於記憶體碎片的警告如下。
  • 如果分配和釋放的記憶體是隨機大小,則不應使用。例如:
  1. 如果應用程式動態建立和刪除任務,並且分配給正在建立的任務的堆疊大小總是相同的,那麼heap2。c在大多數情況下都可以使用。但是,如果分配給要建立的任務的堆疊大小不總是相同的,那麼可用的空閒記憶體可能會分散成許多小塊,最終導致分配失敗。heap_4.c在這種情況下是更好的選擇。
  2. 如果應用程式動態建立和刪除佇列,並且佇列儲存區域在每種情況下都是相同的(佇列儲存區域是佇列項大小乘以佇列長度),那麼heap_2。c在大多數情況下都可以使用。但是,如果佇列儲存區域在每種情況下都不相同,那麼可用的空閒記憶體可能會分散成許多小塊,最終導致分配失敗。heap_4.c在這種情況下是更好的選擇。
  3. 應用程式直接呼叫pvPortMalloc()和vPortFree(),而不是通過其他FreeRTOS API函式間接呼叫。
  • 如果應用程式佇列、任務、訊號量、互斥等順序不可預知,可能會導致記憶體碎片問題。這對於幾乎所有的應用程式來說都不太可能,但應該記住這一點。
  • 不是確定性的——但是比大多數標準的C庫malloc實現更高效。
    heap_2.c語言適用於許多需要動態建立物件的小型實時系統。請參閱heap4.c瞭解將空閒記憶體塊組合成單個更大的塊的類似實現。下面分析heap2.c程式碼
/* 定義連線列表的結構,  用來按他們的大小來連結空閒的記憶體塊 */
typedef struct A_BLOCK_LINK{
	struct A_BLOCK_LINK *pxNextFreeBlock;	/*<< The next free block in the list. */
	size_t xBlockSize;						/*<< The size of the free block. */
} BlockLink_t;

/* 插入一個空閒記憶體塊到連結串列中,這個連結串列是按照空閒記憶體塊的大小來排序的,小塊在開始,大塊在後*/
#define prvInsertBlockIntoFreeList( pxBlockToInsert )								\
{																					\
BlockLink_t *pxIterator;															\
size_t xBlockSize;																	\
																					\
	xBlockSize = pxBlockToInsert->xBlockSize;										\
	/* Iterate through the list until a block is found that has a larger size */	\
	/* than the block we are inserting. 遍歷連結串列,直到找到一個比他大的記憶體*/				            \
	for( pxIterator = &xStart; \
		pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; \
		pxIterator = pxIterator->pxNextFreeBlock )	\
	{																				\
		/* There is nothing to do here - just iterate to the correct position. */	\
	}																				\
																					\
	/* Update the list to include the block being inserted in the correct */		\
	/* position. */																	\
	pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;					\
	pxIterator->pxNextFreeBlock = pxBlockToInsert;									\
}
/*-----------------------------------------------------------*/

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void *pvReturn = NULL;
	vTaskSuspendAll();{
		/*第一次呼叫pvPortMalloc的話,需要初始化建立空閒記憶體列表 */
		if( xHeapHasBeenInitialised == pdFALSE ){
			prvHeapInit();
			xHeapHasBeenInitialised = pdTRUE;
		}

		/* 保證需要的記憶體對齊。 */
		if( xWantedSize > 0 ){
			xWantedSize += heapSTRUCT_SIZE;
			/* Ensure that blocks are always aligned to the required number of bytes. */
			if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 ){
				/* Byte alignment required. */
				xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
			}
		}/*需要多少位元組需要加上 heapSTRUCT_SIZE 算*/

		if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) ){
			/* 記憶體塊是按位元組大小順序儲存的 - 從開始遍歷整個列表,直到最後找出足夠的記憶體塊 */
			pxPreviousBlock = &xStart;
			pxBlock = xStart.pxNextFreeBlock;
			while( ( pxBlock->xBlockSize < xWantedSize ) 
					&& ( pxBlock->pxNextFreeBlock != NULL ) ){
				pxPreviousBlock = pxBlock;
				pxBlock = pxBlock->pxNextFreeBlock;
			}
			/* 如果找到了結尾標誌 then 說明沒有找到合適大小的記憶體塊 */
			if( pxBlock != &xEnd ){
				/* 返回申請的記憶體空間地址 - 要在開始地址跳過 BlockLink_t 所佔的結構*/
				pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );

				/* 這塊記憶體已經被返回使用了所以必須從空閒記憶體列表中移除 */
				pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

				/* 如果申請到的記憶體塊比需要的多 heapMINIMUM_BLOCK_SIZE 以上,那他可以被分為兩塊,
				一塊被返回了一塊被用作下一次分配 */
				if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
				{
					/* 這塊空閒記憶體被分為兩塊,  在被請求的記憶體塊後面新增一塊新的記憶體*/
					pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );

					/* 計算從單個記憶體分割出來的兩塊空閒記憶體大小 */
					pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
					pxBlock->xBlockSize = xWantedSize;

					/* 將新的記憶體空閒塊插入到孔新年記憶體列表中 */
					prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
				}
				xFreeBytesRemaining -= pxBlock->xBlockSize;/* 剩餘的記憶體需要更新 */
			}
		}
	}
	( void ) xTaskResumeAll();
	return pvReturn;
}
/*-----------------------------------------------------------*/

void vPortFree( void *pv ){
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
	if( pv != NULL ){
		/* 被釋放的記憶體有個BlockLink_t結構在他前面 */
		puc -= heapSTRUCT_SIZE;
		vTaskSuspendAll();{
			/* Add this block to the list of free blocks. */
			prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
			xFreeBytesRemaining += pxLink->xBlockSize;
			traceFREE( pv, pxLink->xBlockSize );
		}
		( void ) xTaskResumeAll();
	}
}
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void ){
	return xFreeBytesRemaining;
}
/*-----------------------------------------------------------*/
void vPortInitialiseBlocks( void ){
	/* This just exists to keep the linker quiet. */
}
/*-----------------------------------------------------------*/

static void prvHeapInit( void ){
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;

	/* 保證起始地址位元組對齊 */
	pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

	/* xStart是用來儲存指向  空閒記憶體列表的第一個元素的 */
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	/* xEnd 是用來標誌空閒記憶體塊的結尾的. */
	xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
	xEnd.pxNextFreeBlock = NULL;

	/* 首先要有一個空閒記憶體塊,來佔滿整個空間 */
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
	pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}
/*-----------------------------------------------------------*/

heap3.c

heap3.c為標準的C庫malloc()和free()函式實現了一個簡單的包裝器,在大多數情況下,選擇的編譯器將提供這個包裝器。包裝器只是在外面停止了排程器 使得 malloc()和free() 函式執行緒安全。
這個實現:

  • 需要連結器設定堆,編譯器庫提供malloc()和free()實現。
  • 是不確定的,C庫的malloc跟free 可能需要的時間不確定。
  • 可能會大大增加RTOS核心程式碼的大小。
    注意FreeRTOSConfig中的configTOTAL_HEAP_SIZE設定。使用heap_3時沒有影響。
void *pvPortMalloc( size_t xWantedSize ){
void *pvReturn;
	vTaskSuspendAll();{
		pvReturn = malloc( xWantedSize );
		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();
//其實還有個記憶體分配失敗的預定義函式,如果記憶體分配失敗可以在這個函式中做相應的操作
	return pvReturn;
}
/*-----------------------------------------------------------*/
void vPortFree( void *pv ){
	if( pv ){
		vTaskSuspendAll();{
			free( pv );
			traceFREE( pv, 0 );
		}
		( void ) xTaskResumeAll();
	}
}

注意這個heap3.c中的pvPortMallocvPortFree 只是在c庫的 mallocfree 外面封裝了兩個系統函式,目的是在執行 malloc 和 free 的時候不會被搶佔,就是所謂的執行緒安全