1. 程式人生 > 實用技巧 >FreeRTOS --(3)記憶體管理 heap2

FreeRTOS --(3)記憶體管理 heap2

在《FreeRTOS --(2)記憶體管理 heap1》知道 heap 1 的記憶體管理其實只是簡單的實現了記憶體對齊的分配策略,heap 2 的實現策略相比 heap 1 稍微複雜一點,不僅僅是提供了分配記憶體的介面,同時也提供了釋放記憶體的介面;

但是 heap 2 的記憶體分配策略中,並沒有提供空閒記憶體的合併策略,對記憶體碎片沒有處理;換句話來說,如果有多次的,大小各異的記憶體申請和釋放的場景下,很可能導致很多記憶體碎片;

1、記憶體大小

和 heap 1 一樣,用於記憶體管理的記憶體大小來自於一個大陣列,陣列的下標就是整個需要被管理的記憶體的大小,這個是和具體晶片所支援的 RAM 大小相關:

configTOTAL_HEAP_SIZE

被管理的記憶體定義為:

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

ucHeap 就是管理的物件;

2、對齊

有的處理器是對記憶體對齊有要求的,比如 ARM-CM3 等,AAPCS規則要求堆疊保持8位元組對齊。給任務分配棧時需要保證棧是8位元組對齊的。所以這裡 FreeRTOS 就需要涉及到對齊操作;針對 ARM-CM3 這類處理器來說,在portmacro.h 檔案中,定義了對齊的位元組數:

/* Hardware specifics. */
#define portBYTE_ALIGNMENT            8

而在 portable.h 中,定義了對應的 Mask(8位元組對齊,那麼都要是 8 的倍數,也就是二進位制的 4'b1000,所以 MASK 是 4'b0111 也就是 0x07):

#if portBYTE_ALIGNMENT == 8
    #define portBYTE_ALIGNMENT_MASK ( 0x0007 )
#endif

和 heap 1 一樣,在處理對齊的時候,由於可能 ucHeap 初始的地址就沒對齊,所以這裡真正可以對齊分配的記憶體的 SIZE 就要做一些調整和妥協,由於是 8 位元組對齊,所以最多妥協的大小就是 8 位元組,也就是真正被管理的記憶體大小隻有configADJUSTED_HEAP_SIZE,這裡可能造成幾個位元組的浪費(浪費多少,取決於ucHeap 初始地址 ),不過為了對齊,也就忽略了;

/* A few bytes might be lost to byte aligning the heap start address. */
#define configADJUSTED_HEAP_SIZE    ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

3、記憶體塊

與 heap 1 不同,heap 2 可以支援分配和釋放,那麼管理記憶體的手段勢必比 heap 1 複雜一些,heap 2 對記憶體進行分塊管理,將每塊記憶體通過一個表徵該記憶體塊的的資料結構表示,以單向連結串列串在一起;

3.1、資料結構

表達一個記憶體塊的資料結構是BlockLink_t,它的定義是:

/* Define the linked list structure.  This is used to link free blocks in order
of their size. */
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 指向下一個記憶體塊的BlockLink_t結構;

xBlockSize 代表本記憶體塊的大小;

3.2、資料結構對齊

當然記憶體塊也需要對齊:

static const uint16_t heapSTRUCT_SIZE    = ( ( sizeof ( BlockLink_t ) + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK );

3.3、記憶體塊 Marker

FreeRTOS 為記憶體管理,定義了兩個BlockLink_t結構體,xStart 和 xEnd:

/* Create a couple of list links to mark the start and end of the list. */
static BlockLink_t xStart, xEnd;

xStart 和 xEnd 僅僅作為 mark,標記記憶體塊的起始和結束;

3.4、可用記憶體

在 heap2 中定義了xFreeBytesRemaining 來代表當前可用於分配的記憶體,每當記憶體被分配出去,這個值會減,記憶體被free 後,該值增加:

/* Keeps track of the number of free bytes remaining, but says nothing about
fragmentation. */
static size_t xFreeBytesRemaining = configADJUSTED_HEAP_SIZE;

4、分配記憶體

和 heap 1 一樣,記憶體分配使用 pvPortMalloc 函式,傳入的是希望拿到的記憶體,返回值拿到的記憶體起始地址,如果分配失敗返回 NULL;

/*-----------------------------------------------------------*/
 
void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
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( xHeapHasBeenInitialised == pdFALSE )
        {
            prvHeapInit();
            xHeapHasBeenInitialised = pdTRUE;
        }
 
        /* 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 += heapSTRUCT_SIZE;
 
            /* Ensure that blocks are always aligned to the required number of bytes. */
            if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
            {
                /* Byte alignment required. */
                xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
            }
        }
 
        if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )
        {
            /* Blocks are stored in byte order - traverse the list from the start
            (smallest) block until one of adequate size is found. */
            pxPreviousBlock = &xStart;
            pxBlock = xStart.pxNextFreeBlock;
            while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
            {
                pxPreviousBlock = pxBlock;
                pxBlock = pxBlock->pxNextFreeBlock;
            }
 
            /* If we found the end marker then a block of adequate size was not found. */
            if( pxBlock != &xEnd )
            {
                /* Return the memory space - jumping over the BlockLink_t structure
                at its start. */
                pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
 
                /* 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 );
 
                    /* 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 ) );
                }
 
                xFreeBytesRemaining -= pxBlock->xBlockSize;
            }
        }
 
        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();
 
    #if( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
            extern void vApplicationMallocFailedHook( void );
            vApplicationMallocFailedHook();
        }
    }
    #endif
 
    return pvReturn;
}
/*-----------------------------------------------------------*/

首先呼叫vTaskSuspendAll(); 來掛起所有任務,不允許程序排程;

接著呼叫prvHeapInit(); 來初始化相關的記憶體管理的連結串列結構:

static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
 
    /* Ensure the heap starts on a correctly aligned boundary. */
    pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
 
    /* 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.pxNextFreeBlock = ( void * ) pucAlignedHeap;
    xStart.xBlockSize = ( size_t ) 0;
 
    /* xEnd is used to mark the end of the list of free blocks. */
    xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
    xEnd.pxNextFreeBlock = NULL;
 
    /* To start with there is a single free block that is sized to take up the
    entire heap space. */
    pxFirstFreeBlock = ( void * ) pucAlignedHeap;
    pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
    pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}

在初始化記憶體相關的結構的時候,首先將 ucHeap 的地址進行對齊操作,得到可以對齊後用於真實的記憶體管理的起始地址為:

pucAlignedHeap

然後初始化 xStart 和 xEnd,這兩個 marker,然後將整個可用的記憶體視為一塊,可用的記憶體的開始地方,放置了一個BlockLink_t結構體並初始化它的 xBlockSize 為之前調整過的configADJUSTED_HEAP_SIZE;

我們在回到pvPortMalloc 的地方,繼續分析;

prvHeapInit()初始化完成後,便可用分配記憶體了;分配記憶體的時候,需要對每一個記憶體塊分配一個標誌它的描述符,也就是BlockLink_t結構體,所以如果要分配xWantedSize,那麼就要分配 :

xWantedSize += heapSTRUCT_SIZE;

然後,對xWantedSize 進行位元組對齊操作;

接下來便進行連結串列搜尋,找到 Size 合適的地方,將其分配出來;

值得注意的是,記憶體塊連結串列是有排序的,開始是xStart後面跟的記憶體塊,記憶體塊由小到大,最後是xEnd;

/*
 * Insert a block into the list of free blocks - which is ordered by size of
 * the block.  Small blocks at the start of the list and large blocks at the end
 * of the list.
 */
#define prvInsertBlockIntoFreeList( pxBlockToInsert )                                \
{                                                                                    \
BlockLink_t *pxIterator;                                                            \
size_t xBlockSize;                                                                    \
                                                                                    \
    xBlockSize = pxBlockToInsert->xBlockSize;                                        \
                                                                                    \
    /* Iterate through the list until a block is found that has a larger size */    \
    /* than the block we are inserting. */                                            \
    for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock )    \
    {                                                                                \
        /* There is nothing to do here - just iterate to the correct position. */    \
    }                                                                                \
                                                                                    \
    /* Update the list to include the block being inserted in the correct */        \
    /* position. */                                                                    \
    pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;                    \
    pxIterator->pxNextFreeBlock = pxBlockToInsert;                                    \
}

繼續看程式碼;

如果 pxBlock 不是 xEnd 的話,那麼說明找到有 Size 大於期望分配的 Size 的 Block 了;

那麼就將返回值:

/* Return the memory space - jumping over the BlockLink_t structure at its start. */
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );

這裡,分配記憶體,能夠實際給呼叫這個 API 介面使用的記憶體要從起始的 Block 地址加上heapSTRUCT_SIZE開始算,因為heapSTRUCT_SIZE已經用來表示這個 Block 的資訊了;

然後判斷剩餘的 SIZE 是否大於最小的可用的空間分配的閾值heapMINIMUM_BLOCK_SIZE:

#define heapMINIMUM_BLOCK_SIZE    ( ( size_t ) ( heapSTRUCT_SIZE * 2 ) )

如果剩餘的記憶體空間還足夠那麼:

/* 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 );
 
    /* 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 ) );
}

使用新的pxNewBlockLink 結構表示摘除 pxBlock 記憶體塊後的下一個記憶體塊,並將其初始化,然後按照排序(從小到大的順序)插入到以 xStart 開始的地方;

所以,被初始化後的記憶體

分配一次的結果是:

5、釋放記憶體

heap2 支援釋放記憶體:

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 -= heapSTRUCT_SIZE;
 
        /* This unexpected casting is to keep some compilers from issuing
        byte alignment warnings. */
        pxLink = ( void * ) puc;
 
        vTaskSuspendAll();
        {
            /* Add this block to the list of free blocks. */
            prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
            xFreeBytesRemaining += pxLink->xBlockSize;
            traceFREE( pv, pxLink->xBlockSize );
        }
        ( void ) xTaskResumeAll();
    }
}

來自使用者釋放的指標 pv 是實際的資料指標,代表這個記憶體的結構體在他前面heapSTRUCT_SIZE 的位置,所以該 pv 的 BlockLink_t 結構體指標 pxLink =( void * )(puc -heapSTRUCT_SIZE);

呼叫 prvInsertBlockIntoFreeList 將其插入到連結串列中;並且更新當前剩餘的記憶體量;

釋放後的記憶體如下所示: