1
Heap_4內存管理機制詳解
首先介紹一下用到的重要的結構體-標記內存塊,在每個存放數據的內存塊前都會有一個這樣的標記結構體。
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK *pxNextFreeBlock; /*< < The next free block in the list. */
size_t xBlockSize; /*< < The size of the free block. */
} BlockLink_t;
里面有兩個變量,pxNextFreeBlock指向下一個內存塊,xBlockSize用來表示它所標記的內存塊大小。
還有一些全局變量,都寫了注釋很好理解,就不多解釋。
//內存堆大小,并字節對齊
static const size_t xHeapStructSize = ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
/* Create a couple of list links to mark the start and end of the list. */
static BlockLink_t xStart, *pxEnd = NULL; //內存堆頭尾
/* Keeps track of the number of free bytes remaining, but says nothing about
fragmentation. */
static size_t xFreeBytesRemaining = 0U; //內存堆剩余大小
static size_t xMinimumEverFreeBytesRemaining = 0U; //歷史剩余大小的最小值
/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize
member of an BlockLink_t structure is set then the block belongs to the
application. When the bit is free the block is still part of the free heap
space. */
static size_t xBlockAllocatedBit = 0; //1這個塊被申請;0這個塊空閑
2
內存堆初始化
首先定義一些臨時變量
BlockLink_t *pxFirstFreeBlock; //整個空閑內存塊之前的標記結構體
uint8_t *pucAlignedHeap; //字節對齊后的起始地址
size_t uxAddress; //相當于臨時變量
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; //拋棄不可用內存塊后總的大小
經過一系列的操作,使初始化后,空閑內存表的起始地址為字節對齊,這里和heap_2不同的地方是,使用了臨時變量uxAddress存儲中間計算出來的一些地址,這里uxAddress存儲的是字節對齊后的初始地址,然后賦值給pucAlignedHeap變量中。
/* Ensure the heap starts on a correctly aligned boundary. */
/*確保字節對齊后的起始地址正確*/
uxAddress = ( size_t ) ucHeap; //獲得內存堆的大小放到uxAddress中
if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) //如果內存堆大小不為0且不在掩模中
{
uxAddress += ( portBYTE_ALIGNMENT - 1 ); //portBYTE_ALIGNMENT = 7
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
xTotalHeapSize -= uxAddress - ( size_t ) ucHeap; //拋棄不可用內存塊后總的大小
}
pucAlignedHeap = ( uint8_t * ) uxAddress; //字節對齊后的起始地址
這部分代碼用來初始化空閑內存表的頭和尾,使頭的下一個內存塊指向字節對齊后的首地址,大小初始化為0;這里的uxAddress變量經過一系列操作以及變成了內存塊的末地址,然后使尾的首地址指向末地址(pxEnd=(void *)uxAddress),大小初始化為0,尾的下一個內存塊為NULL。
/*初始化鏈表頭和尾*/
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap; //下一個頭指向字節對齊后的起始地址
xStart.xBlockSize = ( size_t ) 0; //大小初始化為0
/* pxEnd is used to mark the end of the list of free blocks and is inserted
at the end of the heap space. */
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize; //這里的uxAddress已經變成了末尾的地址
uxAddress -= xHeapStructSize; //減去一個標志結構體的大小
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK ); //字節對齊
pxEnd = ( void * ) uxAddress; //這里的uxAddress已經變成了內存堆尾-一個標志結構體然后字節對齊后的地址
pxEnd- >xBlockSize = 0; //末尾的內存塊大小初始化為0
pxEnd- >pxNextFreeBlock = NULL; //下一個指向NULL
在申請內存的最開始,把整個內存堆都看成一個整體,作為一個大內存塊,這個內存塊之前也需要有一個標記結構體,也就是pxFirstFreeBlock結構體,這里對這個結構體進行初始化,它的首地址就是字節對齊后的地址,大小是尾地址uxAddess-內存塊字節對齊后的首地址,下一個內存塊指向pxEnd。
/*開始的時候將內存堆整個可用空間看成一個空閑內存塊*/
pxFirstFreeBlock = ( void * ) pucAlignedHeap; //空閑內存塊之前的標記結構體地址
pxFirstFreeBlock- >xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock; //標記結構體記錄內存塊大小為末地址-初地址
pxFirstFreeBlock- >pxNextFreeBlock = pxEnd; //下一個空閑內存塊為末尾內存塊指針
最后這里就是更新一下全局變量,并標記一下塊被占用。
/*只有一個內存塊,而且這個內存塊擁有內存堆的整個可用空間*/
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock- >xBlockSize; //記錄最小的空閑內存塊大小
xFreeBytesRemaining = pxFirstFreeBlock- >xBlockSize; //記錄歷史最小的空閑內存塊大小
/* Work out the position of the top bit in a size_t variable. */
xBlockAllocatedBit = ( ( size_t ) 1 ) < < ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 ); //初始化靜態變量,初始化完成以后此變量值為 0X80000000
//在 heap_4 中其最高位表示此內存塊是否被使用,如果為 1 的話就表示被使用了,所以在 heap_4 中一個內存塊最大只能為 0x7FFFFFFF
借用一下原子手冊的圖解:
3
插入空閑內存表函數
先定義兩個用到的局部變量,pxIterator相當于C++中容器的迭代器,puc就是個臨時變量。
BlockLink_t *pxIterator; //相當于查找合適位置的迭代器
uint8_t *puc; //相當于臨時變量
這里就是使用迭代器一次次循環,知道找到空閑內存表中滿足內存要求(pxIterator->pxNextFreeBlock < pxBlockToInsert)的內存塊地址。
//遍歷空閑內存塊鏈表,找出內存塊插入點,內存塊按照地址從低到高連接在一起(迭代器思想)
for( pxIterator = &xStart; pxIterator- >pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator- >pxNextFreeBlock )
{
/* Nothing to do here, just iterate to the right position. */
}
這里是判斷要插入的這塊內存和前一塊內存是否相鄰,如果相鄰就合并成一塊,判斷是否相鄰的條件是puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert,插入點地址+這塊內存的大小==要插入塊首地址;即上一塊末地址==要插入塊起始地址
//插入內存塊,如果要插入的內存塊可以和前一個內存塊合并的話就
//合并兩個內存塊
puc = ( uint8_t * ) pxIterator; //找到的合適的插入點的地址
if( ( puc + pxIterator- >xBlockSize ) == ( uint8_t * ) pxBlockToInsert ) //插入點地址+這塊內存的大小==要插入塊首地址;即上一塊末地址==要插入塊起始地址
{
pxIterator- >xBlockSize += pxBlockToInsert- >xBlockSize; //大小合并
pxBlockToInsert = pxIterator; //合并后內存首地址不變
}
else
{
mtCOVERAGE_TEST_MARKER();
}
再借用一下原子的圖:
這一部分代碼是檢查要插入的內存塊是否和后一塊內存相鄰,如果相鄰就合并起來,判斷條件是puc + pxBlockToInsert->xBlockSize == ( uint8_t * ) ( pxIterator->pxNextFreeBlock ),要插入塊首地址+這塊內存的大小==下一塊首地址;即要插入塊末地址==下一塊起始地址
//檢查是否可以和后面的內存塊合并,可以的話就合并
puc = ( uint8_t * ) pxBlockToInsert; //要插入的內存塊的首地址
if( ( puc + pxBlockToInsert- >xBlockSize ) == ( uint8_t * ) pxIterator- >pxNextFreeBlock ) 要插入塊首地址+這塊內存的大小==下一塊首地址;即要插入塊末地址==下一塊起始地址
{
if( pxIterator- >pxNextFreeBlock != pxEnd ) //下一塊不是表尾
{
/* Form one big block from the two blocks. */
//將兩個內存塊組合成一個大的內存塊時
pxBlockToInsert- >xBlockSize += pxIterator- >pxNextFreeBlock- >xBlockSize; 內存塊大小合并
pxBlockToInsert- >pxNextFreeBlock = pxIterator- >pxNextFreeBlock- >pxNextFreeBlock;//合并起來之后下下快變成了下一塊
}
else
{
pxBlockToInsert- >pxNextFreeBlock = pxEnd; //要插入的變成表尾
}
}
else
{
pxBlockToInsert- >pxNextFreeBlock = pxIterator- >pxNextFreeBlock;
}
最后借用一下原子的圖:
如果和前后都不相鄰,則使用最簡單的插入方法:
//在內存塊插入的過程中沒有進行過一次內存合并,使用最簡單的插入方法
if( pxIterator != pxBlockToInsert )
{
pxIterator- >pxNextFreeBlock = pxBlockToInsert;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
4
內存申請函數
先初始化一下內存堆:
//第一次調用,初始化內存堆
if( pxEnd == NULL )
{
prvHeapInit();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
判斷一下想要插入數據的內存塊是否被使用,就是和xBlockAllocateBit變量做一次與運算,如果結果不是1,則說明沒被使用;在確保要插入的大小大于0之后,需要附加上標記結構體的大小(8字節)后,再進行字節對齊。
//需要申請的內存塊大小的最高位不能為 1,因為最高位用來表示內存塊有沒有被使用
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
/* The wanted size is increased so it can contain a BlockLink_t
structure in addition to the requested amount of bytes. */
if( xWantedSize > 0 )
{
xWantedSize += xHeapStructSize; //要申請的大小加上標記結構體的大小
/* Ensure that blocks are always aligned to the required number
of bytes. */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
/* Byte alignment required. */
/*要插入的內存塊字節對齊*/
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
當我們想要插入的內存塊小于剩余內存大小時,就開始查找滿足要求的內存塊。
if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
{
/* Traverse the list from the start (lowest address) block until
one of adequate size is found. */
//從 xStart(內存塊最小)開始,查找大小滿足所需要內存的內存塊
pxPreviousBlock = &xStart; //上一個內存塊
pxBlock = xStart.pxNextFreeBlock; //滿足要求的內存塊(下一塊)
while( ( pxBlock- >xBlockSize < xWantedSize ) && ( pxBlock- >pxNextFreeBlock != NULL ) )
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock- >pxNextFreeBlock;
}
如果找到的是pxEnd表示沒有內存可以分配,否則就將內存首地址保存在 pvReturn 中,函數返回的時候返回此值,然后將這塊內存從空閑內存表中刪除
//如果找到的內存塊是 pxEnd 的話就表示沒有內存可以分配
if( pxBlock != pxEnd )
{
/* Return the memory space pointed to - jumping over the
BlockLink_t structure at its start. */
//找到內存塊以后就將內存首地址保存在 pvReturn 中,函數返回的時候返回此值
//找到的內存塊讓出一個標志結構體的大小
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock- >pxNextFreeBlock ) + xHeapStructSize );
/* This block is being returned for use so must be taken out
of the list of free blocks. */
//將申請到的內存塊從空閑內存鏈表中移除
pxPreviousBlock- >pxNextFreeBlock = pxBlock- >pxNextFreeBlock; //把滿足要求的pxBlock塊的下一塊拼接到上一塊
申請的內存大小小于空閑的一大塊內存的大小,則將其分割,剩下的留著,相當于給空閑內存塊的首地址做一個地址偏移:新的空閑內存塊=滿足要求的內存塊首地址+需要的內存塊首地址,然后更新新的空閑內存塊的大小,并將其插入到空閑內存表。
//如果申請到的內存塊大于所需的內存,就將其分成兩塊
if( ( pxBlock- >xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
/* This block is to be split into two. Create a new
block following the number of bytes requested. The void
cast is used to prevent byte alignment warnings from the
compiler. */
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize ); //新的空閑內存塊=滿足要求的內存塊首地址+需要的內存塊首地址
configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
/* Calculate the sizes of two blocks split from the
single block. */
pxNewBlockLink- >xBlockSize = pxBlock- >xBlockSize - xWantedSize; //更新新的空閑內存塊的大小
pxBlock- >xBlockSize = xWantedSize; //滿足要求的內存塊的大小
/* Insert the new block into the list of free blocks. */
prvInsertBlockIntoFreeList( pxNewBlockLink ); //插入新的空閑內存塊
}
else
{
mtCOVERAGE_TEST_MARKER();
}
最后就是更新一下全局變量
xFreeBytesRemaining -= pxBlock- >xBlockSize; //更新最小內存塊大小
if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ) //更新歷史最小內存塊大小
{
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* The block is being returned - it is allocated and owned
by the application and has no "next" block. */
//內存塊申請成功,標記此內存塊已經被使用
pxBlock- >xBlockSize |= xBlockAllocatedBit; //將pxBlock最高位置1
pxBlock- >pxNextFreeBlock = NULL; //滿足要求的內存塊下一塊指向NULL
后面還可以配置內存申請失敗時的鉤子函數,需要把configUSE_MALLOC_FAILED_HOOK宏打開
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
5
內存釋放函數
先定義一些用到的局部變量:
uint8_t *puc = ( uint8_t * ) pv; //傳入要釋放內存的地址
BlockLink_t *pxLink; //包含了標志結構體后的首地址
傳入的數據地址沒包含標志結構體,需要先做減法,進行地址移位,然后將包含了標志結構體的首地址保存在pxLink中
puc -= xHeapStructSize; //釋放的部分包括上標志結構體大小
/* This casting is to keep the compiler from issuing warnings. */
pxLink = ( void * ) puc; //防止編譯器報錯
如果要釋放的內存真的被使用,就開始釋放操作,先把首位變0,表示變成空閑,然后更新空閑內存大小,將這塊內存插入回空閑內存表中,要注意:釋放和申請內存,并不是把這塊內存從一個鏈表中拿出來了,只是做了一些標記,讓程序知道這部分被占用,有數據,在釋放內存之前我們將數據刪除,然后把標志位改為空閑狀態就行,這就是釋放的本質。
if( ( pxLink- >xBlockSize & xBlockAllocatedBit ) != 0 ) //判斷是否真被使用
{
if( pxLink- >pxNextFreeBlock == NULL )
{
/* The block is being returned to the heap - it is no longer
allocated. */
pxLink- >xBlockSize &= ~xBlockAllocatedBit; //首位變0,表示未被使用
vTaskSuspendAll();
{
/* Add this block to the list of free blocks. */
/*將內存塊插到空閑內存鏈表中*/
xFreeBytesRemaining += pxLink- >xBlockSize; //更新最小內存塊大小
traceFREE( pv, pxLink- >xBlockSize );
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) ); //將被釋放的內存塊插入空閑內存鏈表中
}
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
6
總結
其他的函數主要就是直接返回我們之前更新的全局變量,終于把基礎知識都鋪墊完了,下面結合具體項目程序談談怎么優化了。
-
FreeRTOS
+關注
關注
12文章
492瀏覽量
63835 -
STM32F103
+關注
關注
33文章
482瀏覽量
65140 -
內存管理
+關注
關注
0文章
168瀏覽量
14496 -
迭代器
+關注
關注
0文章
45瀏覽量
4443
發布評論請先 登錄
第28章 FreeRTOS動態內存管理
FreeRTOS vPortFree 內存釋放異常怎么辦
基于FreeRTOS內存管理Heap_4.c的實現方法
FreeRTOS代碼剖析之4:內存管理Heap
FreeRTOS代碼剖析之3:內存管理Heap
STM32F103芯片資料介紹

GD32F103與STM32F103的區別 2021.6.2

評論