FreeRTOS的heap_4記憶體管理演算法具有記憶體碎片合併的功能,可以有效防止記憶體碎片產生,使用First fit演算法,在實現上與C標準庫的malloc類似,但是效率更高且能進行碎片合併回收。以下是個人對源碼的解析,有空再補充詳細。 一、初始化 static void prvHeapInit( vo ...
FreeRTOS的heap_4記憶體管理演算法具有記憶體碎片合併的功能,可以有效防止記憶體碎片產生,使用First fit演算法,在實現上與C標準庫的malloc類似,但是效率更高且能進行碎片合併回收。以下是個人對源碼的解析,有空再補充詳細。
一、初始化
static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
/*====================================== 1 ===========================================*/
/* 位元組對齊,4位元組 */
uxAddress = ( size_t ) ucHeap;
/*位元組對齊,一般是8位元組*/
if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
{
/* 對齊處理 */
uxAddress += ( portBYTE_ALIGNMENT - 1 );
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
}
/*取對齊後的地址*/
pucAlignedHeap = ( uint8_t * ) uxAddress;
/*====================================== 2 ===========================================*/
/* 把xStart的next指針指向對齊後的頭地址,長度設置為0.xStart只是鏈表頭不參與記憶體分配*/
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
/*====================================== 3 ===========================================*/
/* 計算尾部指針地址 */
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
/* 減去end所占用的8個位元組 */
uxAddress -= xHeapStructSize;
/* pxend位元組對齊,也就是尾部會空出8-15位元組用於放pxend */
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
/* pxend初始化 */
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
/*====================================== 4 ===========================================*/
/* 初始化頭結構,也就是xstart一開始指向的那個地址 */
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
/* 初始化記憶體最大使用量和剩餘空間這兩個變數的值 */
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
/* 定義xBlockSize最高bit,因為xBlockSize的最高bit用於判斷是否使用 */
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
- 進行位元組對齊,找到對齊後的首地址,在32位機中以8位元組進行對齊。
- 初始化xStart的值。
- 計算對齊後的尾部地址,將pxEnd指向這一地址,同時初始化。
- 初始化xStart指向的頭地址的值,因為還沒分配,所以next指向pxend,size為整個空間大小。初始化用於記錄剩餘空間的變數值
二、申請記憶體
void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
{
/* 如果還沒初始化的話,就先初始化. */
if( pxEnd == NULL )
{
prvHeapInit();
}
/* 檢查要分配的大小是否超過了最大值,因為最高位用來標誌空閑塊是否已經使用,
所以能分配的空間最大值為0x7FFF FFFF 也就是2G*/
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
/* 檢查分配空間是否為0 */
if( xWantedSize > 0 )
{
/* 加上鏈表結構的大小 */
xWantedSize += xHeapStructSize;
/* 日常位元組對齊 */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
/* 補齊. */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
}
/* 這裡也判斷xWantedSize>0,可以跟上面的代碼合併啊,判斷空閑的空間還夠不夠 */
if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
{
/* 從頭開始查找大小夠分配的空閑塊,直到找到pxend. */
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
/* 如果是pxEnd就是說沒有能夠分配的空閑塊了,分配失敗 */
if( pxBlock != pxEnd )
{
/* 分配的地址是空閑塊管理結構地址+結構大小,如圖
分配了的空間 新的空閑塊
|____|_______________|________________|
☝ ↑分配的記憶體地址
有足夠空間的結構, */
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
/* 跳過剛剛被使用的空閑塊,指向下一塊 */
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
/* 如果當前空閑塊分配完之後剩餘的大小還>=16位元組,就分成兩塊 */
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
/* 創建一個新的空閑塊,計算偏移地址 */
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
/* 初始化新空閑塊的大小,next需要做插入處理 */
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
/* 舊塊重新定義大小 */
pxBlock->xBlockSize = xWantedSize;
/* Insert the new block into the list of free blocks.看英語解釋 */
prvInsertBlockIntoFreeList( pxNewBlockLink );
}
/* 扣除剩餘的空間統計 */
xFreeBytesRemaining -= pxBlock->xBlockSize;
/* 記錄當前使用空間的最大值,也就是記錄系統運行中最多用了多少空間 */
if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
{
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
/* 最高位置為1,清楚next指針,標記已經用掉了 */
pxBlock->xBlockSize |= xBlockAllocatedBit;
pxBlock->pxNextFreeBlock = NULL;
}
}
}
}
{
if( pvReturn == NULL )
{
printf("malloc fail \r\n");
}
}
return pvReturn;
}
三、釋放記憶體
oid vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
if( pv != NULL )
{
/* 找到結構體的地址
↓puc地址
|______|___________________|
↑BlockLink_t地址*/
puc -= xHeapStructSize;
/* 防一手編譯器警告 */
pxLink = ( void * ) puc;
/* 通過最高位判斷是否已經使用 */
if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
{
/* 已經使用的next被覆製為null,可以看malloc */
if( pxLink->pxNextFreeBlock == NULL )
{
/*清掉標誌位 */
pxLink->xBlockSize &= ~xBlockAllocatedBit;
{
/* 統計空閑內記憶體大小,插入鏈表中. */
xFreeBytesRemaining += pxLink->xBlockSize;
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
}
}
}
}
}
/*-----------------------------------------------------------*/
四、碎片整理
把新的空閑列表項插入鏈表中,同時進行空閑塊合併。
static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
BlockLink_t *pxIterator;
uint8_t *puc;
/* 遍歷鏈表,找到newlist的前一個list地址,也就是插入的位置.
heap4對鏈表的地址管理都是從小到大,所以只要迴圈比對地址大小就行了 */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
/* Nothing to do here, just iterate to the right position. */
}
/* 插入前,檢查前(已有的項)後(要插入的項)兩個空閑塊是否相鄰,相鄰的話直接合併 */
puc = ( uint8_t * ) pxIterator;
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
/* 合併處理 */
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator;
}
/* 插入前,檢查前(要插入的項pxBlockToInsert)後(已有的項)兩個空閑塊是否相鄰,相鄰的話直接合併,
跟上面的流程相同,只是比對的是跟在新鏈表後面的那個 */
puc = ( uint8_t * ) pxBlockToInsert;
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
if( pxIterator->pxNextFreeBlock != pxEnd )
{
/* 合成一塊 */
pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
}
else
{
/* 不合併的話給新鏈表項的next賦值 */
pxBlockToInsert->pxNextFreeBlock = pxEnd;
}
}
else
{
/* 不合併的話給新鏈表項的next賦值 */
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
}
/* 如果沒進行過合併,插入新鏈表 */
if( pxIterator != pxBlockToInsert )
{
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
}