FreeRTOS 之 動態內存管理(heap_1.c)詳解

寫在前面

  寫這篇文章時,網上已經有了鋪天蓋地的文章來介紹 FreeRTOS 的動態內存。之所以還去寫這篇博文,主要還是記錄自己的學習過程。結合源代碼一步一步分析一下FreeRTOS究竟是怎麼實現的其內存部分。

簡介

  FreeRTOS 支持 5 種動態內存管理方案,分別通過文件 heap_1.cheap_2.cheap_3.cheap_4.cheap_5.c 實現。這 5 個文件在 FreeRTOS 源碼包中的路徑是:FreeRTOS\Source\portable\MemMang。具體如下圖:
位置
用戶在自己的項目中如果要使用 FreeRTOS ,則必須從中以上5中內存方案中選擇一種 。5種方案各有各的優勢,分別適用於不同的應用場景。
  再具體的實現上,FreeRTOS 內核規定的幾個內存管理函數原型。系統內部及用戶如果要使用內存,只能通過該函數接口進行申請。因此完全可以有用戶自己實現。具體函數接口如下(不同方案稍有區別):

  • void *pvPortMalloc( size_t xSize ) :內存申請函數
  • void vPortFree( void *pv ) :內存釋放函數
  • void vPortInitialiseBlocks( void ) :初始化內存堆函數
  • size_t xPortGetFreeHeapSize( void ):獲取當前未分配的內存堆大小
  • size_t xPortGetMinimumEverFreeHeapSize( void ):獲取未分配的內存堆歷史最小值

heap_1.c

  這種內存分配方式最簡單直接,速度快程序簡單。適用於分配完內存後不需要回收的場合。只允許管理一個靜態的數組ucHeap,內存從靜態Ram中由系統分配,不能指定管理外部SRAM,或者管理堆中的內存。下面就結合源碼,詳細來介紹一下heap_1.c

配置

  在 FreeRTOS 的配置文件(FreeRTOSConfig.h)中,關於內存管理部分的配置項主要有以下幾個,要使用FreeRTOS 提供的內存實現策略,則必須進行有效的配置:

  • configSUPPORT_STATIC_ALLOCATION
    設置爲1,那麼可以使用應用程序編寫器提供的RAM創建RTOS對象。設置爲0,則只能使用從FreeRTOS堆分配的RAM創建RTOS對象。默認(未定義時)爲0
  • configSUPPORT_DYNAMIC_ALLOCATION
    設置爲1,則可以使用從FreeRTOS堆中自動分配的RAM創建RTOS對象。設置爲0,則只能使用應用程序編寫器提供的RAM創建RTOS對象。默認(未定義)爲1
  • configTOTAL_HEAP_SIZE
    FreeRTOS堆中可用的RAM總量。該值僅在configSUPPORT_DYNAMIC_ALLOCATION設置爲 1 且應用程序使用FreeRTOS源代碼下載中提供的示例內存分配方案之一時該定義纔有效。 有關詳細信息,請參閱內存配置部分。
  • configAPPLICATION_ALLOCATED_HEAP
    默認情況下,FreeRTOS堆由FreeRTOS聲明,並由鏈接器放置在內存中。 將configAPPLICATION_ALLOCATED_HEAP設置爲1允許應用程序編寫器聲明堆,這允許應用程序將堆放置在內存中的任何位置。
    如果使用heap_1.c,heap_2.c或heap_4.c,並且configAPPLICATION_ALLOCATED_HEAP設置爲1,那麼應用程序編寫器必須提供一個具有完全名稱和維度的uint8_t數組,如下所示:uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
    該數組將用作FreeRTOS堆。 如何將數組放置在特定的內存位置取決於使用的編譯器 - 請參閱您的編譯器文檔。

  從上面的配置不難看出,FreeRTOS給與了用戶極大的權限(配置靈活性),甚至允許完全由用戶自己實現內存堆的管理。例如,使用使用靜態分配方式(版本9.0.0之後)。
  heap_1.c是這是 5 種內存管理方案中最簡單的一個,簡單到只能申請內存(沒有釋放)。heap_1.c方案中,內存堆實際上是一個很大的數組,名爲:static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];。源碼如下:

/* heap_1.h的內存管理方式,只允許管理一個靜態的數組ucHeap,內存從靜態Ram中由系統分配 */
#if( configSUPPORT_DYNAMIC_ALLOCATION == 0 )
	#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0
#endif

/* 首地址按 portBYTE_ALIGNMENT 對齊後內存容量的大小 */
#define configADJUSTED_HEAP_SIZE	( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

/* Allocate the memory for the heap. */
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
	/* 用戶自定義靜態內存的位置,名稱必須爲ucHeap. */
	extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
	/* 如果 沒有啓用用戶自定義,則使用默認的 */
	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */

/* Index into the ucHeap array. 記錄已經分配的內存大小(主要用來定位下一個空閒的內存堆位置) */
static size_t xNextFreeByte = ( size_t ) 0;

具體的說明,見上面的註釋即可。

內存對齊

  在 portmacro.h (Source/Portable/ + 對應編譯器 + 平臺 目錄下) 的常量 portBYTE_ALIGNMENT 定義了字節對齊,對應的這個變量決定了portable.h 中的一個常量 portBYTE_ALIGNMENT_MASK, 對應關係如下:

portBYTE_ALIGNMENT portBYTE_ALIGNMENT_MASK
32(表示以32個字節對齊) 0x001f
16(表示以16個字節對齊) 0x000f
8(表示以8個字節對齊) 0x0007
4(表示以4個字節對齊) 0x0003
2(表示以2個字節對齊) 0x0001
1(表示以1個字節對齊) 0x0000

至於爲什麼會考慮內存對齊呢?這個在LwIP的博文中有詳細的說明,再次不在贅述!

void *pvPortMalloc( size_t xWantedSize )

這個函數是由 FreeRTOS 規定的內存分配函數。系統內部及用戶如果要使用內存,只能通過該函數接口進行申請。

void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
/* 按 portBYTE_ALIGNMENT 對齊後內存首地址。!!!注意這是個靜態變量,在第一次使用時會被初始化!!! */
static uint8_t *pucAlignedHeap = NULL;	

	/* 確保指定的靜態內存塊是 按 portBYTE_ALIGNMENT 對齊的(如果指定的對齊爲1字節,就沒必要處理了). */
	#if( portBYTE_ALIGNMENT != 1 )
	{
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )	/* 其實就是xWantedSize % portBYTE_ALIGNMENT */
		{
			/* Byte alignment required.將 xWantedSize 按照 portBYTE_ALIGNMENT 強制對齊 */
			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;
}

malloc

void vPortFree( void *pv )

  這個函數是由 FreeRTOS 規定的內存釋放函數。系統內部及用戶如果要釋放內存,只能通過該函數接口進行申請。有下面的實現可知,heap_1.c方案,內存一旦申請便無法釋放!

void vPortFree( void *pv )
{
	/* Memory cannot be freed using this scheme.  See heap_2.c, heap_3.c and
	heap_4.c for alternative implementations, and the memory management pages of
	http://www.FreeRTOS.org for more information. */
	( void ) pv;

	/* Force an assert as it is invalid to call this function. */
	configASSERT( pv == NULL );
}

void vPortInitialiseBlocks( void )

  這個函數是由 FreeRTOS 規定的內存管理初始化函數。

void vPortInitialiseBlocks( void )
{
	/* Only required when static memory is not cleared. */
	xNextFreeByte = ( size_t ) 0;
}

size_t xPortGetFreeHeapSize( void )

  這個函數是由 FreeRTOS 規定的獲取動態內存的剩餘大小的函數。唯一需要注意的就是,其大小是在對齊之後的!

size_t xPortGetFreeHeapSize( void )
{
	return ( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章