【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(); /* 退出臨界 */
}

