1. 程式人生 > >FreeRTOS程式碼閱讀筆記:heap_4.c

FreeRTOS程式碼閱讀筆記:heap_4.c

FreeRTOS中對於記憶體的管理當前一共有5種實現方式(作者當前的版本是10.1.1),均在【 \Source\portable\MemMang 】下面,這裡筆記下。

heap_4.c和第二種方式比較相似,只不過增加了一個和並演算法,將相鄰空閒記憶體合併為一個大記憶體,和方法一、二管理策略一樣,記憶體堆仍為一個大陣列。

區別在於第四種記憶體管理策略的空閒塊連結串列不是以記憶體塊大小為儲存順序,而是以記憶體塊起始地址大小為儲存順序,地址小的在前,地址大的在後。這也是為了適應合併演算法而作的改變。

使用方法:
標頭檔案:FreeRTOSConfig.h 
配置引數:
configTOTAL_HEAP_SIZE             定義系統所用的堆疊大小。
configUSE_MALLOC_FAILED_HOOK      預設0: 1則開啟鉤子函式,記憶體分配失敗則呼叫 
 
函式呼叫:	
      vPortInitialiseBlocks();//初始化
      ptr=pvPortMalloc(1024);
	  if(ptr !=NULL)
	  {
	  freemem=xPortGetFreeHeapSize(); 
	  printf("剩餘記憶體 %d \r\n",i,freemem); 
	  }
	  else
	  {
	  printf("獲取記憶體失敗\r\n");break;

重要引數:

重要的引數備註:
(1)FreeRTOS  記憶體堆為:ucHeap[] 大小為 configTOTAL_HEAP_SIZE 
(2)pucAlignedHeap 作為堆疊位元組對齊後的起始地址(怎麼實現的思考一下)
(3)xTotalHeapSize 表示堆疊進行記憶體大小減去被捨棄位元組後的總記憶體大小
(4)xStart, xEnd; 記錄空閒連結串列的首尾。
(5)pxFirstFreeBlock 空閒連結串列
(6)xMinimumEverFreeBytesRemaining 是一個統計值,儲存了當前模擬堆執行時的最小剩餘記憶體容量
(7)xFreeBytesRemaining:當前模擬堆的剩餘記憶體容量

舉個例子:
記憶體申請:
空閒鏈是按照地址排序的,從連結串列中找出記憶體大小滿足的記憶體塊。相對於方式二其查詢時間可能變長了。
記憶體釋放:
釋放記憶體後會比較前後的空閒塊是否地址連線,是則記憶體合併。

heap_2.c分析:

將原始碼解析調整一下順序:

下面是連結串列的初始化,heap_2.c中連結串列的尾部資料並未儲存在連結串列內,是以變數的形式存在的。heap_4.c中的連結串列尾部資料結構儲存在連結串列空間尾部。

下面是連結串列初始化程式碼:

//關於堆疊的初始化
//第一步:起始地址做位元組對齊  儲存pucAlignedHeap 可用空間大小為xTotalHeapSize 
//第二步:計算首尾 ,這裡需要注意的是連結串列的尾部指標是儲存到該地址尾部的
//第三部:完成連結串列的初始化,記錄記憶體塊資訊

static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;

	/* Ensure the heap starts on a correctly aligned boundary. */
	//起始地址做位元組對齊處理
	uxAddress = ( size_t ) ucHeap;
	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;         //對齊後可以用的起始地址

	/* xStart is used to hold a pointer to the first item in the list of free
	blocks.  The void cast is used to prevent compiler warnings. */
	//xStart連結串列的頭 
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap; 
	xStart.xBlockSize = ( size_t ) 0;

	/* pxEnd is used to mark the end of the list of free blocks and is inserted
	at the end of the heap space. */
	//pxEnd連結串列的尾
	uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
	uxAddress -= xHeapStructSize;
	uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
	pxEnd = ( void * ) uxAddress;                       
	pxEnd->xBlockSize = 0;
	pxEnd->pxNextFreeBlock = NULL;

	/* To start with there is a single free block that is sized to take up the
	entire heap space, minus the space taken by pxEnd. */
	//開始時候將記憶體堆整個看作為一個空閒記憶體塊
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
	pxFirstFreeBlock->pxNextFreeBlock = pxEnd;

	/* Only one block exists - and it covers the entire usable heap space. */
	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 );//用來標記記憶體塊是否被使用
}

 空閒塊連結串列的插入:會判斷前後的空閒塊能否合併,解決記憶體碎片化的問題。

static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
BlockLink_t *pxIterator;
uint8_t *puc;

	/* Iterate through the list until a block is found that has a higher address
	than the block being inserted. */
	//遍歷空閒記憶體塊連結串列,找出記憶體塊插入點。記憶體塊是按照地址從低到高連線在一起的
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
	{
		/* Nothing to do here, just iterate to the right position. */
	}

	/* Do the block being inserted, and the block it is being inserted after
	make a contiguous block of memory? */
	//插入記憶體塊,檢查和前面的記憶體是否可以合併,如果記憶體可以合併則合併
	puc = ( uint8_t * ) pxIterator;
	if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
	{
		pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
		pxBlockToInsert = pxIterator;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* Do the block being inserted, and the block it is being inserted before
	make a contiguous block of memory? */
	//檢查是否可以與後面的記憶體合併
	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 the block being inserted plugged a gab, so was merged with the block
	before and the block after, then it's pxNextFreeBlock pointer will have
	already been set, and should not be set here as that would make it point
	to itself. */
	//如果不能合併的話,就普通處理
	if( pxIterator != pxBlockToInsert )
	{
		pxIterator->pxNextFreeBlock = pxBlockToInsert;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

實現程式碼

#include <stdlib.h>
 
/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining
all the API functions to use the MPU wrappers.  That should only be done when
task.h is included from an application file. */
#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE
 
#include "FreeRTOS.h"
#include "task.h"
 
#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE
 
/* Block sizes must not get too small. */
#define heapMINIMUM_BLOCK_SIZE  ( ( size_t ) ( xHeapStructSize * 2 ) )
 
/*
1個byte有8個bits
*/
/* Assumes 8bit bytes! */
#define heapBITS_PER_BYTE       ( ( size_t ) 8 )
 
/*
內部用來模擬堆的也是一個大號的陣列,這裡可以通過外部的記憶體來指定這個大號的陣列。
*/
/* Allocate the memory for the heap. */
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
    /* The application writer has already defined the array used for the RTOS
    heap - probably so it can be placed in a special segment or address. */
    extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
    static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */
 
/* Define the linked list structure.  This is used to link free blocks in order
of their memory address. */
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其實也是放在它對應的空餘記憶體的頭部的,但是空閒記憶體的大小並沒有考慮放入的BlockLink_t */
} BlockLink_t;
//空閒記憶體管理結構體,通過它來管理釋放回來的記憶體
 
/*-----------------------------------------------------------*/
 
/*
 * Inserts a block of memory that is being freed into the correct position in
 * the list of free memory blocks.  The block being freed will be merged with
 * the block in front it and/or the block behind it if the memory blocks are
 * adjacent to each other.
 */
static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert );
 
/*
 * Called automatically to setup the required heap structures the first time
 * pvPortMalloc() is called.
 */
static void prvHeapInit( void );
 
/*-----------------------------------------------------------*/
 
/* 考慮到位元組對齊後BlockLink_t的大小 */
/* The size of the structure placed at the beginning of each allocated memory
block must by correctly byte aligned. */
static const size_t xHeapStructSize = ( ( sizeof( BlockLink_t ) + ( ( ( size_t ) portBYTE_ALIGNMENT_MASK ) - ( size_t ) 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;
 
/*-----------------------------------------------------------*/
 
void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
 
    vTaskSuspendAll();//首先暫停當前所有執行的任務
    {
        /* If this is the first call to malloc then the heap will require
        initialisation to setup the list of free blocks. */
        if( pxEnd == NULL )//如果是第一次執行該函式,那麼先呼叫下初始化函式
        {
            prvHeapInit();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
 
        /* Check the requested block size is not so large that the top bit is
        set.  The top bit of the block size member of the BlockLink_t structure
        is used to determine who owns the block - the application or the
        kernel, so it must be free. */
        /*這裡xWantedSize的大小有要求,需要最高位為0。因為後面BlockLink_t結構體中的xBlockSize的最高位需要使用。      
        */
        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;/* 空餘記憶體的頭部要放一個BlockLink_t來管理,因此這裡需要人為的擴充下申請的記憶體大小 */
 
                /* 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. */
             
              //從空餘記憶體連結串列的頭部開始找,如果該空餘記憶體的大小>xWantedSize,就標記該記憶體為pxBlock   
                pxPreviousBlock = &xStart;
                pxBlock = xStart.pxNextFreeBlock;
                while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
                {
                    pxPreviousBlock = pxBlock;
                    pxBlock = pxBlock->pxNextFreeBlock;
                }
 
                /* If the end marker was reached then a block of adequate size
                was not found. */
                /* 如果pxBlock不是pxEnd,說明上面的while已經找到了合適的記憶體 */
                if( pxBlock != pxEnd )
                {
                    /* 找到了,就把該塊記憶體返回給使用者,注意記憶體的頭部有BlockLink_t,需要偏移掉 */
                    /* Return the memory space pointed to - jumping over the
                    BlockLink_t structure at its start. */
                    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;
 
                    /* If the block is larger than required it can be split into
                    two. */
                    //如果剩下的記憶體大小符合要求,就把它放到空餘記憶體連結串列中
                    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( ( ( ( uint32_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. */
                    //注意這裡的xBlockSize的最高位被設定為1,標記了該記憶體是pvPortMalloc返回的。                 
                    pxBlock->xBlockSize |= xBlockAllocatedBit;
                    pxBlock->pxNextFreeBlock = NULL;
                }
                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
 
    configASSERT( ( ( ( uint32_t ) pvReturn ) & portBYTE_ALIGNMENT_MASK ) == 0 );
    return pvReturn;
}
/*-----------------------------------------------------------*/
 
void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
 
    if( pv != NULL )
    {
        /* The memory being freed will have an BlockLink_t structure immediately
        before it. */
        puc -= xHeapStructSize;//這裡向前偏移,重新找回BlockLink_t
 
        /* This casting is to keep the compiler from issuing warnings. */
        pxLink = ( void * ) puc;
 
        /* Check the block is actually allocated. */
        configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
        configASSERT( pxLink->pxNextFreeBlock == NULL );
         
        /* 如果xBlockSize的最高位為1,說明該塊記憶體是pvPortMalloc申請的,那麼通過pxBlock->xBlockSize |= xBlockAllocatedBit;
        可以知道該塊記憶體的真實大小為pxLink->xBlockSize &= ~xBlockAllocatedBit,即最高位改回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;
 
                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//如果xBlockSize的最高位不為1,那麼說明該塊記憶體不是通過pvPortMalloc申請的,那麼直接忽略處理
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
}
/*-----------------------------------------------------------*/
 
size_t xPortGetFreeHeapSize( void )
{
    return xFreeBytesRemaining;
}
/*-----------------------------------------------------------*/
 
size_t xPortGetMinimumEverFreeHeapSize( void )
{
    return xMinimumEverFreeBytesRemaining;
}
/*-----------------------------------------------------------*/
 
void vPortInitialiseBlocks( void )
{
    /* This just exists to keep the linker quiet. */
}
/*-----------------------------------------------------------*/