寫在前面
寫這篇文章時,網上已經有了鋪天蓋地的文章來介紹 FreeRTOS 的動態內存。之所以還去寫這篇博文,主要還是記錄自己的學習過程。結合源代碼一步一步分析一下FreeRTOS究竟是怎麼實現的其內存部分。
簡介
FreeRTOS 支持 5 種動態內存管理方案,分別通過文件 heap_1.c
,heap_2.c
,heap_3.c
,heap_4.c
和 heap_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;
}
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 );
}