【freertos】008-記憶體管理
- 前言
- 8.1 C標準庫的記憶體管理
- 8.2 freertos記憶體管理介面
- 8.3 freertos五種記憶體管理
- 8.4 heap5記憶體管理實現細節
前言
本章主要講解內部存儲空間(RAM)的管理。
詳細分析heap5方案。
參考:
8.1 C標準庫的記憶體管理
C標準庫的記憶體管理用到的API是malloc()
和free()
,但是不建議在RTOS中直接調用,因為:
- C標準庫的記憶體管理實現可能比較大,不適合小型嵌入式RAM不足的設備。
- 可能會產生記憶體碎片,對於安全性要求高的嵌入式設備不適合。
- 這兩個函數會使得鏈接器配置得複雜。
- 待補充。
8.2 freertos記憶體管理介面
freertos的記憶體管理和內核實現是相互獨立的,內核規定記憶體管理介面,而介面內容卻是可由外部自由實現。
但是freertos官方也提供了幾種記憶體分配演算法:heap1、heap2、heap3、heap4、heap5。
所以,需要記憶體管理的有合適的演算法可以單獨使用freertos提供記憶體分配演算法到自己的設備或系統中。
記憶體堆大小由宏configTOTAL_HEAP_SIZE
決定。(heap3方案除外)
/*
* Map to the memory management routines required for the port.
*/
void * pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION; //記憶體申請函數
void vPortFree( void * pv ) PRIVILEGED_FUNCTION; //記憶體釋放函數
void vPortInitialiseBlocks( void ) PRIVILEGED_FUNCTION; //初始化記憶體堆函數
size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION; //獲取當前未分配的記憶體堆大小
size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION; //獲取未分配的記憶體堆歷史最小值
8.3 freertos五種記憶體管理
簡單介紹。
8.3.1 heap1
特點:
- 只能申請不能釋放。
- 不會產生記憶體碎片。
- 函數的執行時間是確定的。因為可直接查到空閑空間地址和大小。
應用:這種方案一般用在安全性要求較高的系統中。用於從不刪除任務、隊列、訊號量、互斥量等的應用程式。
實現:
使用xNextFreeByte
來定位下一個空閑的記憶體堆位置。
因為freertos系統堆是一個大數組,所以,記憶體空間是連續的。
所以xNextFreeByte
值在heap1方案中實際保存的是已經被分配的記憶體大小,下次申請時跳過這些已申請的,剩下就是空閑空間。
pucAlignedHeap
是一個指向對齊後的記憶體堆起始地址。
用戶提供的系統堆記憶體的起始地址不一定是對齊的記憶體地址,需要糾正,糾正後的系統堆記憶體起始地址保存在pucAlignedHeap
。
static size_t xNextFreeByte = ( size_t ) 0;
static uint8_t *pucAlignedHeap = NULL;
API注意:
vPortInitialiseBlocks()
僅僅將靜態局部變數xNextFreeByte
設置為0,表示記憶體沒有被申請。xPortGetFreeHeapSize()
並不是釋放記憶體,因為heap1方案不支援釋放,所以該API是獲取當前未分配的記憶體堆大小。
8.3.2 heap2
特點:
- 支援動態申請和釋放。
- 採用最佳匹配演算法,即是會遍歷鏈表,選擇出最小符合用戶申請的記憶體塊交給用戶。
- 鏈表管理,但是不支援拼接相鄰空閑塊。所以容易產生記憶體碎片。
- 申請時間具有不確定性,因為檢索空閑塊需要檢索鏈表,空閑塊多、記憶體碎片多時,檢索會久點。但是效率比標準C庫中的malloc函數高得多。
應用:不建議用於記憶體分配和釋放是隨機大小的應用程式。
實現:
heap2方案的記憶體管理鏈表:
typedef struct A_BLOCK_LINK {
struct A_BLOCK_LINK *pxNextFreeBlock;
size_t xBlockSize;
} BlockLink_t;
pxNextFreeBlock
:是指向下一個空閑記憶體塊的指針。
xBlockSize
:記錄當前記憶體塊大小。(記憶體管理函鏈表結構體)
8.3.3 heap3
特點:
- 需要鏈接器設置一個堆,malloc()和free()函數由編譯器提供。
- 具有不確定性。
- 很可能增大RTOS內核的程式碼大小。
configTOTAL_HEAP_SIZE
不起作用,因為堆空間由編譯器決定提供的。一般在啟動文件里設置。
實現:
heap3方案只是簡單的封裝了標準C庫中的malloc()和free()函數。
重新封裝後的malloc()和free()函數具有保護功能,採用的封裝方式是操作記憶體前掛起調度器、完成後再恢復調度器。
8.3.4 heap4
和heap2方案類似,且支援相鄰空閑塊拼接,降低記憶體塊碎片化幾率。
特點:
- 支援動態申請和釋放。
- 按地址升序,優先返回第一個滿足size需求的空閑塊。
- 鏈表管理,支援相鄰空閑塊拼接。
- 申請時間具有不確定性,因為檢索空閑塊需要檢索鏈表,但是效率比標準C庫中的malloc函數高得多。
應用:
- 可用於重複刪除任務、隊列、訊號量、互斥量等的應用程式。
- 可用於分配和釋放隨機位元組記憶體的應用程式。
8.3.5 heap5
heap_5.c方案在實現動態記憶體分配時與heap4.c方案一樣,採用最佳匹配演算法和合併演算法。
並且允許記憶體堆跨越多個非連續的記憶體區。
如可以在片內RAM中定義一個記憶體堆,還可以在外部SDRAM再定義一個或多個記憶體堆,這些記憶體都歸系統管理。
heap1、heap2、heap4的堆空間都是有個大數組,拓展下,支援非連續的記憶體堆,可以使用多個大數組啊。
特點:
- 支援動態申請和釋放。
- 按地址升序,優先返回第一個滿足size需求的空閑塊。
- 鏈表管理,支援相鄰空閑塊拼接。
- 支援記憶體堆跨越多個非連續的記憶體區。
- 申請時間具有不確定性,因為檢索空閑塊需要檢索鏈表,但是效率比標準C庫中的malloc函數高得多。
實現:
各塊記憶體堆管理結構體:
typedef struct HeapRegion
{
uint8_t *pucStartAddress; // 記憶體堆起始地址
size_t xSizeInBytes; // 記憶體塊大小
} HeapRegion_t;
初始化後的記憶體如下:
8.4 heap5記憶體管理實現細節
實現的記憶體管理特點如下:
- 支援動態申請和動態釋放。
- 按地址升序,優先返回第一個滿足size需求的空閑塊。
- 支援相鄰空閑塊拼接。
- 支援多個非連續堆空間。
- 注意:在創建內核組件前,先調用
vPortDefineHeapRegions()
完成系統堆初始化。
8.4.1 相關介面
/* Map to the memory management routines required for the port. */
/* 先初始化多個非連續堆空間 */
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions ) PRIVILEGED_FUNCTION;
/* 記憶體申請函數 */
void * pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION;
/* 記憶體釋放函數 */
void vPortFree( void * pv ) PRIVILEGED_FUNCTION;
void vPortInitialiseBlocks( void ) PRIVILEGED_FUNCTION; // 初始化記憶體堆函數
size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION; // 獲取當前未分配的記憶體堆大小
size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION; // 獲取未分配的記憶體堆歷史最小值
void vPortGetHeapStats( HeapStats_t *xHeapStats ); // 提供堆狀態資訊
8.4.2 相關參數
xFreeBytesRemaining
:表示當前系統中未分配的記憶體堆大小。
xMinimumEverFreeBytesRemaining
:表示未分配記憶體堆空間歷史最小的記憶體值。為了了解最壞情況下記憶體堆的使用情況。
xBlockAllocatedBit
:系統最高位比特位1。如在32位系統下,該變數值位0x80000000。
- 通常用法:噹噹前記憶體塊被佔用時,當前記憶體塊size記錄值會與該變數按位或,標誌當前記憶體塊被佔用。
xStart
:記憶體管理塊鏈表頭節點,用於指向第一個記憶體塊。
pxEnd
:記憶體管理塊鏈表尾節點指針,用於表示後面沒有合法記憶體空間。
xNumberOfSuccessfulAllocations
:申請成功的次數。
xHeapStructSize
:某個記憶體塊的管理結構的size,該size也是(向上)位元組對齊的。
heapMINIMUM_BLOCK_SIZE
:記憶體塊size下限。(記憶體塊管理區+數據區),在heap5方案中是兩倍的xHeapStructSize
,即是數據區最小也要有一個xHeapStructSize
大小。
8.4.3 數據結構
8.4.3.1 各非連續堆塊管理數據結構
各大塊堆空間管理數據結構類型HeapRegion_t
:
pucStartAddress
:該塊堆空間記憶體起始地址。xSizeInBytes
:該塊堆空間大小。
typedef struct HeapRegion
{
uint8_t * pucStartAddress;
size_t xSizeInBytes;
} HeapRegion_t;
例如,如果定義兩個非連續堆空間:
- 第一個記憶體塊大小為0x10000位元組,起始地址為0x80000000;
- 第二個記憶體塊大小為0xa0000位元組,起始地址為0x90000000。
- 按照地址順序放入到數組中,地址小的在前。
- 該數據結構供給堆空間初始化API
vPortDefineHeapRegions()
使用。
const HeapRegion_t xHeapRegions[] = {
{ ( uint8_t * ) 0x80000000UL, 0x10000 },
{ ( uint8_t * ) 0x90000000UL, 0xa0000 },
{ NULL, 0 } /* 數組結尾 */
};
8.4.3.2 記憶體塊管理數據結構
記憶體塊管理數據結構類型BlockLink_t
:
-
pxNextFreeBlock
:是指向下一個空閑記憶體塊的指針。 -
xBlockSize
:記錄當前記憶體塊大小。(記憶體管理函鏈表結構體)- 需要特別注意的是,這個記憶體塊大小不僅僅代表當前記憶體塊可用空間,還表示當前記憶體塊是否空閑:該變數最高位為0時,表示空閑,為1時,表示被佔用。(參考:
heapBITS_PER_BYTE
)
- 需要特別注意的是,這個記憶體塊大小不僅僅代表當前記憶體塊可用空間,還表示當前記憶體塊是否空閑:該變數最高位為0時,表示空閑,為1時,表示被佔用。(參考:
-
就是單向鏈表。
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK * pxNextFreeBlock; /* 下一個空閑記憶體塊的指針 */
size_t xBlockSize; /* 當前記憶體塊大小 */
} BlockLink_t
8.4.3.3 主要數據
除了數據結構類型外,管理數據結構還需要一些變數來實現。
鏈表頭xStart
:
- 記憶體塊鏈表頭。
static BlockLink_t xStart;
鏈表尾指針pxEnd
:
- 記憶體塊鏈表尾。
static BlockLink_t *pxEnd = NULL;
8.4.4 初始化堆空間:vPortDefineHeapRegions()
函數原型:
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
-
pxHeapRegions
:傳入保存非連續堆空間地址的數據結構地址。HeapRegion_t
:各非連續塊堆空間管理數據結構類型。
8.4.4.1 數據校驗
初始時記憶體塊鏈表尾指針為空,表示沒有被初始化過,沒有被使用過才能重置這些堆塊。
configASSERT( pxEnd == NULL );
如果有堆塊被初始化過了,記憶體塊鏈表尾指針不應該為空。
同時,新的堆塊地址在舊塊後。
/* 初始化非首個堆塊 */
if(xDefinedRegions != 0)
{
/* 如果初始化過堆塊,肯定有尾節點的 */
configASSERT( pxEnd != NULL );
/* 新的堆塊起始地址要在前面堆塊尾地址後 */
configASSERT( xAddress > ( size_t ) pxEnd );
}
8.4.4.2 地址對齊
用戶傳入的堆塊空間始末地址不一定符合地址對齊的,在初始化時需要裁剪,使堆塊地址向內對齊。
首地址向上對齊:
if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) /* 如果還沒對齊,需要實現向上對齊 */
{
/* 先向上漂移到下一個對齊空間 */
xAddress += ( portBYTE_ALIGNMENT - 1 );
/* 去除餘數,即是往當前對齊空間下對齊 */
xAddress &= ~portBYTE_ALIGNMENT_MASK;
/* 更新對齊後,可用的實際空間。即是減去對齊丟棄的空間 */
xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress;
}
/* 對齊後的堆塊起始地址 */
xAlignedHeap = xAddress;
同時,在堆塊尾部需要預留出尾部鏈表節點的空間,該空間起始地址也需要符合地址對齊。
8.4.4.3 頭節點:xStart
記憶體管理塊鏈表的頭節點未xStart
,該節點只是一個哨兵,不含數據,只指向第一個堆塊。
該節點的實際空間並不在堆塊內,而記憶體管理塊節點和xpEnd
指向的尾節點的空間都是存在堆塊內部。
8.4.4.4 記憶體管理塊
記憶體管理塊數據結構內容參考前面。
剛剛初始化的堆塊記憶體,一整塊都是空閑空間,但是這些空間需要記憶體管理塊數據結構來管理。
heap5方案與其它方案不一樣,需要支援不連續地址,,其實現是在堆塊尾部需要預留一個記憶體管理塊節點空間出來,用於對接下一個堆塊。
所以初始化完堆塊後,其內部結構如下圖所示:
8.4.4.5 完整程式碼實現
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions )
{
BlockLink_t * pxFirstFreeBlockInRegion = NULL, * pxPreviousFreeBlock;
size_t xAlignedHeap;
size_t xTotalRegionSize, xTotalHeapSize = 0;
BaseType_t xDefinedRegions = 0;
size_t xAddress;
const HeapRegion_t * pxHeapRegion;
/* 沒有被初始化過才能往下執行 */
configASSERT( pxEnd == NULL );
/* 獲取第一個堆塊的地址 */
pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );
/* 逐塊初始化 */
while( pxHeapRegion->xSizeInBytes > 0 )
{
/* 記錄當前堆塊空間大小 */
xTotalRegionSize = pxHeapRegion->xSizeInBytes;
/* 確保各堆塊起始地址符合對齊要求 */
xAddress = ( size_t ) pxHeapRegion->pucStartAddress;
if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) /* 如果還沒對齊,需要實現向上對齊 */
{
/* 先向上漂移到下一個對齊空間 */
xAddress += ( portBYTE_ALIGNMENT - 1 );
/* 去除餘數,即是往當前對齊空間下對齊 */
xAddress &= ~portBYTE_ALIGNMENT_MASK;
/* 更新對齊後,可用的實際空間。即是減去對齊丟棄的空間 */
xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress;
}
/* 對齊後的堆塊起始地址 */
xAlignedHeap = xAddress;
if( xDefinedRegions == 0 ) /* 初始化首個堆塊 */
{
/* xStart為哨兵節點,不帶數據,只帶指向首個堆塊 */
xStart.pxNextFreeBlock = ( BlockLink_t * ) xAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
}
else /* 初始化非首個堆塊 */
{
/* 如果初始化過堆塊,肯定有尾節點的 */
configASSERT( pxEnd != NULL );
/* 新的堆塊起始地址要在前面堆塊尾地址後 */
configASSERT( xAddress > ( size_t ) pxEnd );
}
/* 備份系統堆塊尾部節點 */
pxPreviousFreeBlock = pxEnd;
/* 在新堆塊末留出尾節點空間,用於表示系統堆塊空間結束點 */
xAddress = xAlignedHeap + xTotalRegionSize;
xAddress -= xHeapStructSize;
xAddress &= ~portBYTE_ALIGNMENT_MASK;
pxEnd = ( BlockLink_t * ) xAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
/* 在當前堆塊內前部空間作為當前堆塊管理數據結構 */
pxFirstFreeBlockInRegion = ( BlockLink_t * ) xAlignedHeap;
/* 當前堆塊當前可給用戶使用的空間大小。(對比原有,少了首位對齊位元組、少了首位記憶體塊管理數據結構空間) */
pxFirstFreeBlockInRegion->xBlockSize = xAddress - ( size_t ) pxFirstFreeBlockInRegion;
/* 當前堆塊的下一個堆塊指向尾節點,表示當前為最後一個堆塊 */
pxFirstFreeBlockInRegion->pxNextFreeBlock = pxEnd;
/* 如果當前不是首個初始化的堆塊,就需要拼接到前面初始化的堆塊中 */
if( pxPreviousFreeBlock != NULL )
{
/* 前一塊堆塊尾部節點的下一個堆塊指向當前堆塊 */
pxPreviousFreeBlock->pxNextFreeBlock = pxFirstFreeBlockInRegion;
}
/* 記錄總堆塊可用空間 */
xTotalHeapSize += pxFirstFreeBlockInRegion->xBlockSize;
/* 移到下一個需要初始化的堆塊 */
xDefinedRegions++;
pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );
}
/* 初始化未分配記憶體堆歷史最小值。用於了解最壞情況下,記憶體堆的使用情況。 */
xMinimumEverFreeBytesRemaining = xTotalHeapSize;
/* 當前系統未分配記憶體堆大小 */
xFreeBytesRemaining = xTotalHeapSize;
/* 系統堆必須有空間可用才能在後面被訪問 */
configASSERT( xTotalHeapSize );
/* 系統最高位標記為1。
如32為系統時,該值為0x80000000。
用於標記記憶體塊是否空閑。被佔用時,將記憶體塊節點內的記憶體塊大小xBlockSize和該值按位或。 */
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
8.4.5 記憶體塊插入空閑鏈表:prvInsertBlockIntoFreeList()
這是一個內部介面函數,用戶不會接觸到,但是後面的malloc和free的實現會使用到,所以在這裡先分析了。
原型
static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert );
8.4.5.1 檢索記憶體塊相鄰的方法
插入空閑塊是按空閑塊地址順序插入的。
需要找到當前空閑塊起始地址比新插入的空閑塊起始地址小,且當前空閑塊鏈表指向的下一個空閑塊的起始地址比新插入的空閑塊起始地址大,新的空閑塊就是需要插入到這兩中間。
/* 先找到和pxBlockToInsert相鄰的前一個空閑塊 */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
/* Nothing to do here, just iterate to the right position. */
}
8.4.5.2 合併記憶體塊
檢查記憶體塊地址連續的方法就是:本塊記憶體起始地址+本塊記憶體大小==下一塊記憶體起始地址。
/* 如果前一個空閑塊和pxBlockToInsert在地址上是連續的,就和前一塊合併 */
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
/* ... */
}
/* 如果pxBlockToInsert和其下一個空閑塊地址連續,就和下一個空閑塊合併 */
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
/* ... */
}
8.4.5.3 完整程式碼實現
static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert )
{
BlockLink_t * pxIterator;
uint8_t * puc;
/* 先找到和pxBlockToInsert相鄰的前一個空閑塊 */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
/* Nothing to do here, just iterate to the right position. */
}
/* 獲取pxBlockToInsert相鄰的前一個空閑塊的起始地址 */
puc = ( uint8_t * ) pxIterator;
/* 如果前一個空閑塊和pxBlockToInsert在地址上是連續的,就和前一塊合併 */
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
/* size先合併 */
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
/* 記憶體管理塊也統一下 */
pxBlockToInsert = pxIterator;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 獲取當前需要插入的空閑塊。(可能是合併後的,也可能是沒有合併的,都是下一個空閑塊的前一個空閑塊) */
puc = ( uint8_t * ) pxBlockToInsert;
/* 如果pxBlockToInsert和其下一個空閑塊地址連續,就和下一個空閑塊合併 */
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
if( pxIterator->pxNextFreeBlock != pxEnd ) /* 如果pxBlockToInsert的下一個空閑塊的下一個空閑節點不是尾節點,便需要合併 */
{
/* 合併size */
pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
/* 更新指向。合併時,pxBlockToInsert指向原有下一個空閑塊的下一個空閑塊 */
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
}
else /* 如果pxBlockToInsert的下一個空閑塊的空閑節點是尾節點 */
{
/* 合併時,不需要擴充size,只需要更新指向即可 */
pxBlockToInsert->pxNextFreeBlock = pxEnd;
}
}
else
{
/* 如果不能合併,就直接插入 */
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
}
/* 如果新插入的空閑塊沒有和前一個空閑塊合併,插入新的空閑塊後需要更新鏈表節點指向 */
if( pxIterator != pxBlockToInsert )
{
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
8.4.6 記憶體申請:pvPortMalloc()
8.4.6.1 原型
void * pvPortMalloc( size_t xWantedSize )
xWantedSize
:輸入用戶需要申請的空間大小。
返回:
- 申請成功後返回用戶可用空間的起始地址。
- 申請失敗返回NULL。
8.4.6.2 參數校驗
用pxEnd
不為空證明該介面對應的堆空間已經被初始化過。
/* 堆塊必須被初始化過 */
configASSERT( pxEnd );
xWantedSize
該值不能大到頂部標誌都被覆蓋掉。
/* 需要申請的記憶體不能大到頂部標誌都被覆蓋掉。 */
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
/* ... */
}
8.4.6.3 參數糾正
xWantedSize
變數先是用戶傳入的期望申請的大小,而內部空閑塊管理除了給用戶使用的數據區外還需要記憶體塊管理區,所以需要添加一個xHeapStructSize
。
/* 一個記憶體塊除了數據區外,還需要管理區BlockLink_t */
xWantedSize += xHeapStructSize;
還需要位元組向上對齊:
- 向上對齊是size往更大方向對齊。
- 防止溢出是防止
xWantedSize
擴大後不能大到覆蓋掉頂部標誌位。
/* 確保申請的記憶體大小也符合位元組對齊 */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
/* 要往上對齊,且要防止溢出 */
if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize )
{
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
else
{
xWantedSize = 0;
}
}
8.4.6.4 拆分記憶體塊
如果檢索到size
符合要求的第一個空閑塊,malloc會檢查該塊除了給用戶使用的空間和記憶體管理塊的空間後,剩餘的空間是否滿足組建新的空閑塊,如果滿足就進行拆分。
/* 如果當前空閑塊滿足用戶需求後,還剩足夠的空間組件另一個空閑塊,那就需要拆分 */
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
/* 組建新的空閑塊,其起始地址為上一個空閑塊地址+xWantedSize偏移就是了 */
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
/* 新的空閑塊節點size處理。 */
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize;
/* 新的空閑塊插入到空閑鏈表中 */
prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
}
8.4.6.5 完整程式碼實現
void * pvPortMalloc( size_t xWantedSize )
{
BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink;
void * pvReturn = NULL;
/* 堆塊必須被初始化過 */
configASSERT( pxEnd );
vTaskSuspendAll();
{
/* 需要申請的記憶體不能大到頂部標誌都被覆蓋掉。 */
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
/* 申請的size大於0,且加上記憶體管理塊後不能溢出 */
if( ( xWantedSize > 0 ) && ( ( xWantedSize + xHeapStructSize ) > xWantedSize ) )
{
/* 一個記憶體塊除了數據區外,還需要管理區BlockLink_t */
xWantedSize += xHeapStructSize;
/* 確保申請的記憶體大小也符合位元組對齊 */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
/* 要往上對齊,且要防止溢出 */
if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize )
{
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
else
{
xWantedSize = 0;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
xWantedSize = 0;
}
/* 申請的記憶體佔用的size要小於現有空閑size才會進行檢索 */
if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
{
/* 從低地址的空閑塊開始檢索 */
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
/* 找出能滿足xWantedSize大小的空閑塊 */
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
/* 確保當前塊不是尾節點 */
if( pxBlock != pxEnd )
{
/* 返回數據區起始地址給用戶 */
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
/* 把當前塊從空閑鏈表中移除 */
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
/* 如果當前空閑塊滿足用戶需求後,還剩足夠的空間組件另一個空閑塊,那就需要拆分 */
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
/* 組建新的空閑塊,其起始地址為上一個空閑塊地址+xWantedSize偏移就是了 */
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
/* 新的空閑塊節點size處理。 */
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize;
/* 新的空閑塊插入到空閑鏈表中 */
prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 更新系統堆剩餘空閑空間 */
xFreeBytesRemaining -= pxBlock->xBlockSize;
/* 更新申請記憶體的歷史最低值 */
if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
{
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 標記當前塊已被使用 */
pxBlock->xBlockSize |= xBlockAllocatedBit;
/* 重置節點指向 */
pxBlock->pxNextFreeBlock = NULL;
/* 更新全局malloc次數 */
xNumberOfSuccessfulAllocations++;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
traceMALLOC( pvReturn, xWantedSize );
}
( void ) xTaskResumeAll();
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
/* 有鉤子就調用下鉤子,一般用於調試或記錄 */
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* if ( configUSE_MALLOC_FAILED_HOOK == 1 ) */
return pvReturn;
}
8.4.7 記憶體釋放
8.4.7.1 原型
void vPortFree( void * pv );
pv
:需要釋放的記憶體空間的起始地址。
8.4.7.2 簡要分析
通過傳入的地址,偏移後找到該記憶體塊的數據管理結構。
檢查傳入的地址是否合法,記憶體塊是否符合特徵。
在heap5程式碼中檢查記憶體塊是否合法的做法還可以添加一個條件,傳入的地址在堆塊範圍內
檢查合法後,把當前記憶體塊回收到空閑鏈表:
- 清除被佔用位。
- 把當前空閑塊插回空閑鏈表。
8.4.7.3 完整程式碼實現
void vPortFree( void * pv )
{
uint8_t * puc = ( uint8_t * ) pv;
BlockLink_t * pxLink;
if( pv != NULL )
{
/* 找到該記憶體塊的數據管理結構 */
puc -= xHeapStructSize;
/* 類型轉換 */
pxLink = ( void * ) puc;
/* 記憶體塊數據管理結構校驗 */
configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
configASSERT( pxLink->pxNextFreeBlock == NULL );
/* 如果當前記憶體塊被分配了,就需要釋放 */
if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
{
if( pxLink->pxNextFreeBlock == NULL )
{
/* 先消除被佔用標誌位,這樣該變數才是表示當前記憶體塊的size */
pxLink->xBlockSize &= ~xBlockAllocatedBit;
vTaskSuspendAll();
{
/* 把當前塊回收到內核,更新空閑空間size */
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
/* 把當前塊插回空閑鏈表 */
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
/* 更新記錄調用free次數 */
xNumberOfSuccessfulFrees++;
}
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
8.4.8 獲取總堆空閑size:xPortGetFreeHeapSize()
size_t xPortGetFreeHeapSize( void )
{
return xFreeBytesRemaining;
}
8.4.9 獲取歷史申請最小空間值:xPortGetMinimumEverFreeHeapSize()
size_t xPortGetMinimumEverFreeHeapSize( void )
{
return xMinimumEverFreeBytesRemaining;
}
8.4.10 獲取堆狀態資訊
狀態資訊結構體:
/* 用於從vPortGetHeapStats()傳遞關於堆的資訊. */
typedef struct xHeapStats
{
size_t xAvailableHeapSpaceInBytes; /* The total heap size currently available - this is the sum of all the free blocks, not the largest block that can be allocated. */
size_t xSizeOfLargestFreeBlockInBytes; /* The maximum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
size_t xSizeOfSmallestFreeBlockInBytes; /* The minimum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
size_t xNumberOfFreeBlocks; /* The number of free memory blocks within the heap at the time vPortGetHeapStats() is called. */
size_t xMinimumEverFreeBytesRemaining; /* The minimum amount of total free memory (sum of all free blocks) there has been in the heap since the system booted. */
size_t xNumberOfSuccessfulAllocations; /* The number of calls to pvPortMalloc() that have returned a valid memory block. */
size_t xNumberOfSuccessfulFrees; /* The number of calls to vPortFree() that has successfully freed a block of memory. */
} HeapStats_t;
介面實現:
void vPortGetHeapStats( HeapStats_t * pxHeapStats )
{
BlockLink_t * pxBlock;
size_t xBlocks = 0, xMaxSize = 0, xMinSize = portMAX_DELAY; /* portMAX_DELAY used as a portable way of getting the maximum value. */
vTaskSuspendAll();
{
pxBlock = xStart.pxNextFreeBlock;
/* 堆塊被初始化過才能進去 */
if( pxBlock != NULL )
{
/* 遍歷每一個空閑記憶體塊 */
do
{
/* 記錄當前有多少個空閑記憶體塊(也包括非連續堆塊末節點的記憶體塊,雖然數據區size為0,但是也記錄在內) */
xBlocks++;
/* 更新最大塊的size */
if( pxBlock->xBlockSize > xMaxSize )
{
xMaxSize = pxBlock->xBlockSize;
}
/* 需要注意的是heap5支援多個非連續的堆塊,所以非連續堆塊末節點值用於指向下一個記憶體塊,其內數據區size為0 */
if( pxBlock->xBlockSize != 0 )
{
/* 更新最小塊size */
if( pxBlock->xBlockSize < xMinSize )
{
xMinSize = pxBlock->xBlockSize;
}
}
/* 遍歷下一個記憶體塊 */
pxBlock = pxBlock->pxNextFreeBlock;
} while( pxBlock != pxEnd );
}
}
( void ) xTaskResumeAll();
/* 收集部分堆狀態資訊 */
pxHeapStats->xSizeOfLargestFreeBlockInBytes = xMaxSize;
pxHeapStats->xSizeOfSmallestFreeBlockInBytes = xMinSize;
pxHeapStats->xNumberOfFreeBlocks = xBlocks;
taskENTER_CRITICAL(); /* 進入臨界。維護堆塊管理的全局變數的原子性 */
{
/* 收集剩餘堆狀態資訊 */
pxHeapStats->xAvailableHeapSpaceInBytes = xFreeBytesRemaining;
pxHeapStats->xNumberOfSuccessfulAllocations = xNumberOfSuccessfulAllocations;
pxHeapStats->xNumberOfSuccessfulFrees = xNumberOfSuccessfulFrees;
pxHeapStats->xMinimumEverFreeBytesRemaining = xMinimumEverFreeBytesRemaining;
}
taskEXIT_CRITICAL(); /* 退出臨界 */
}