freeRTOS小內存堆算法深入講解

1、內存堆算法簡介

       本文所介紹的內存堆管理是freeRTOS操作系統中的小內存管理算法,參考heap_4.c源文件。這個程序適用於小內存的CPU,比如像STM32F這樣的只有幾十-幾百KB內存的處理器。整個內存堆的處理算法簡潔,高效,現對其中的原理做詳細的介紹。此算法和RT Thread操作系統的小內存管理算法是一樣,可以參考這篇文章,只是代碼實現的方式上有一些區別。

       heap_4.c文件的由4個函數組成。

  •   void *pvPortMalloc( size_t xWantedSize )
  •   void vPortFree( void *pv )
  •   static void prvHeapInit( void )
  •     static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )

2、內存堆初始化函數prvHeapInit

       prvHeapInit函數對程序預先定義的數組ucHeap進行初始化,數組大小爲10KB,把這個數組做內存堆。在本工程中編譯器給ucHeap數組分配的空間是0x20000038-0x20002837。

       prvHeapInitt函數對內存堆的空間ucHeap進行初始化,首先對使用的數組起始地址按照8字節對齊進行,那麼取到的內存堆的起始地址就是0x20000040,因爲對齊的原因數組的前8字節就浪費不能使用。

/* 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, pxEnd是一個heap_4.c中靜態變量,用於管理整個內存堆的結構。BlockLink_t是一個內存管理數據結構,佔用8個字節的空間,結構體指針佔4字節,分配出去的內存大小變量佔4字節。

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

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

      函數下面對ucHeap的數據0x20000040開始的地址使用前8字節做爲內存堆管理的數據結構,前4個字節是pxNextFreeBlock,指向下一個內存堆管理數據結構,後4個字節xBlockSize是這個內存堆可以使用的空間大小。xStart.pxNextFreeblock指向0x20000040這個地址,xStart這個變量是一個空閒內堆管理變量。

      在ucHeap數據的最後8字節也是用於管理內存堆,這8個字節的起始地址(0x20002830)也是要求8字節對齊,佔用這8個字節用於指示內存堆的結尾,pxEnd指針指向這個地址。

      內存堆的整個可用空間就是整個數組的長度,減去內存堆尾控制結構pxEnd佔用的8字節和起始地址因爲對齊浪費的8字節。xBlockAllocatedBit是一個4字節的整形變量,最高位設置爲1,表示內存已經分配出去。

     

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

    /* pxEnd is used to mark the end of the list of free blocks and is inserted
    at the end of the heap space. */
    pucHeapEnd = pucAlignedHeap + xTotalHeapSize;
    pucHeapEnd -= heapSTRUCT_SIZE;
    pxEnd = ( void * ) pucHeapEnd;
    configASSERT( ( ( ( uint32_t ) pxEnd ) & ( ( uint32_t ) portBYTE_ALIGNMENT_MASK ) ) == 0UL );
    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 = xTotalHeapSize - heapSTRUCT_SIZE;
    pxFirstFreeBlock->pxNextFreeBlock = pxEnd;

    /* The heap now contains pxEnd. */
    xFreeBytesRemaining -= heapSTRUCT_SIZE;

    /* Work out the position of the top bit in a size_t variable. */
    xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );

      此函數執行完成後,內存堆的空間分佈如下圖。

3、內存分配函數pvPortMalloc

        函數開始的幾行代碼對內存堆進行初始,當第一次調用pvPortMalloc來分配內存時,內存堆未初始化,pxEnd==NULL,所以要進行內存堆的初始化,調用prvHeapInit函數。

  BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
  void *pvReturn = NULL;      
  /* 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();
        }

          

       內存堆的核心分配代碼如下,主要是算法思想就是,從xStart指向的空閒鏈表內存塊中查找一個大小滿足的內存,如果這塊內存大小合適就直接分配出去,如果內存大很多,把這個內存分成2塊,一塊大小滿足需要分配的內存大小,另一塊插入到xStart指向的空閒鏈表內存塊中去。

  

/* 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. */
        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 )
            {
                /*分配的內存長度的基礎上增加一個8字節的內存管理數據結構的大小*/
                xWantedSize += heapSTRUCT_SIZE;

                /* Ensure that blocks are always aligned to the required number
                of bytes. */
                if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
                {
                    /* Byte alignment required. 分配的長度要8字節對齊*/
                    xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
                }
                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. */
                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. */
                if( pxBlock != pxEnd )
                {
                    /* Return the memory space pointed to - jumping over the
                    BlockLink_t structure at its start. */
                    /*找到一塊內存可以分配,把這塊內存開始的8字節後分配給用戶程序使用,開始8字節做爲內存管理數據結構*/
                    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. */
                    /*判斷可供分配的內存如果比用戶申請的內存大2倍的內存管理數據結構大小即16字節,就對這個內存塊進行拆分,分成一塊用戶需要的大小分配出去,另外一塊插入到空閒內存鏈表中*/
                    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 ) );
                    }
                    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. */
                    /*分配出去的內存,內存控制數據結構中大小的最高位設置爲1,表示已經分配*/
                    pxBlock->xBlockSize |= xBlockAllocatedBit;
                    pxBlock->pxNextFreeBlock = NULL;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        //traceMALLOC( pvReturn, xWantedSize );
    }
    


    return pvReturn;

       經過連續4次內存分配並且中間不釋放內存後的內存堆分佈如下圖。此時內存是連續使用,沒有內存碎片產生。

4、內存釋放函數rt_free

      函數前面幾行對要釋放的內存指針進行合法性檢測,判斷內存指針是否有效。

       釋放一塊內存的操作很簡單,即對內存控制塊的xBlockSize的最高位清零,pxLink->xBlockSize &= ~xBlockAllocatedBit;。最後把這個內存塊放到空閒內存鏈表中去

uint8_t *puc = ( uint8_t * ) pv;
    BlockLink_t *pxLink;
    CPU_SR_ALLOC();

    if( pv != NULL )
    {
        /* The memory being freed will have an BlockLink_t structure immediately
        before it. */
        puc -= heapSTRUCT_SIZE;

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

        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;

                

                                
                    /* Add this block to the list of free blocks. */
                    xFreeBytesRemaining += pxLink->xBlockSize;
                    //traceFREE( pv, pxLink->xBlockSize );
                    prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
                
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

        在申請4次內存後,釋放第二次申請的內存後,內存堆中內存分佈如下圖。

 

5、插入空閒內存鏈表函數prvInsertBlockIntoFreeList   

      函數實現把一個內存塊插入到空閒內存鏈表中去。

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. */
    /*從xStart所指向的空閒鏈表頭開始查找,根據內存控制塊的地址由小到大排列的順序,找到中間的位置進行插入,插入後還保持地址爲由小到大的順序*/
    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();
    }

       在上圖中釋放一次2號內存後,再釋放3號內存,這兩個塊內存相鄰,prvInsertBlockIntoFreeList函數會對這兩塊內存進行合併。如下圖。

6、兩種算法的比較分析

     freeRTOS和RT thread的小內存堆的管理算法的思想是一樣子的,使用的程序編寫方法,內存控制塊的結構並不一樣,各自有各自的優缺點。

     A、內存控制塊的大小,  RT Thread內存控制塊佔用12字節,freeRTOS佔用8字節,相比來說freeRTOS對內存堆的利用率更高。

    B、內存分配函數中對大塊內存的拆分的標準一樣的,都是分配用戶內存後剩餘空間超過2倍央存控制塊的大小後就對內存塊進行拆分。

    C、代碼的複雜度, RT Thread的內存堆管理代碼相對更復雜一點,函數入口做了更多的參數檢查和斷言,如果出現內存泄露會更容易查找問題。

 

發佈了37 篇原創文章 · 獲贊 79 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章