FreeRTOS內(nèi)存管理

FreeRTOS系列第8篇---FreeRTOS內(nèi)存管理
該博客中貼了代碼。

內(nèi)存管理對應(yīng)用程序和操作系統(tǒng)來說都非常重要?,F(xiàn)在很多的程序漏洞和運行崩潰都和內(nèi)存分配使用錯誤有關(guān)。FreeRTOS操作系統(tǒng)將內(nèi)核與內(nèi)存管理分開實現(xiàn),操作系統(tǒng)內(nèi)核僅規(guī)定了必要的內(nèi)存管理函數(shù)原型,而不關(guān)心這些內(nèi)存管理函數(shù)是如何實現(xiàn)的。

每當(dāng)創(chuàng)建任務(wù)、隊列、互斥量、軟件定時器、信號量或事件組時,RTOS內(nèi)核會為它們分配RAM。標(biāo)準(zhǔn)函數(shù)庫中的malloc()和free()函數(shù)能夠用于完成這個任務(wù),但是:

  • 在嵌入式系統(tǒng)中,它們并不總是可以使用的;
  • 它們會占用更多寶貴的代碼空間;
  • 它們沒有線程保護;
  • 它們不具有確定性(每次調(diào)用執(zhí)行的時間可能會不同);
    ?為什么有這些問題?

FreeRTOS內(nèi)核規(guī)定的幾個內(nèi)存管理函數(shù)原型為:

  • void *pvPortMalloc( size_t xSize ) :內(nèi)存申請函數(shù)
  • void vPortFree( void *pv ) :內(nèi)存釋放函數(shù)
  • void vPortInitialiseBlocks( void ) :初始化內(nèi)存堆函數(shù)
  • size_t xPortGetFreeHeapSize( void ) :獲取當(dāng)前未分配的內(nèi)存堆大小
  • size_t xPortGetMinimumEverFreeHeapSize( void ):獲取未分配的內(nèi)存堆歷史最小值

因此,提供一個替代的內(nèi)存分配方案通常是必要的。 嵌入式/實時系統(tǒng)具有千差萬別的RAM和時間要求,因此一個RAM內(nèi)存分配算法可能僅屬于一個應(yīng)用的子集。
FreeRTOS提供的內(nèi)存分配方案分別位于不同的源文件(heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c)之中。

一、 FreeRTOS 內(nèi)存 方案

1.1 heap_1.c

  • 用于從不會刪除任務(wù)、隊列、信號量、互斥量等的應(yīng)用程序(實際上大多數(shù)使用FreeRTOS的應(yīng)用程序都符合這個條件)
  • 執(zhí)行時間是確定的并且不會產(chǎn)生內(nèi)存碎片
  • 實現(xiàn)和分配過程非常簡單,需要的內(nèi)存是從一個靜態(tài)數(shù)組中分配的,意味著這種內(nèi)存分配通常只是適用于那些不進行動態(tài)內(nèi)存分配的應(yīng)用。

一旦分配內(nèi)存之后,它甚至不允許釋放分配的內(nèi)存。大多數(shù)深度嵌入式(deeplyembedded)應(yīng)用只是在系統(tǒng)啟動時創(chuàng)建所有任務(wù)、隊列、信號量等,并且直到程序結(jié)束都會一直使用它們,永遠(yuǎn)不需要刪除。

API函數(shù)xPortGetFreeHeapSize()返回未分配的堆??臻g總大小,可以通過這個函數(shù)返回值對configTOTAL_HEAP_SIZE進行合理的設(shè)置。

static size_t xNextFreeByte = ( size_t ) 0;//記錄已經(jīng)非陪的內(nèi)存大小,用來定位下一空閑的內(nèi)存堆位置
static uint8_t *pucAlignedHeap = NULL;//堆對齊,這樣訪問速度更快
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL; 
 
    /* 確保申請的字節(jié)數(shù),宏portBYTE_ALIGNMENT 是對齊字節(jié)數(shù),默認(rèn)為8 */
    #if( portBYTE_ALIGNMENT != 1 )
    { 
//進行位與運算,來判斷是否為8字節(jié)對齊,等于0就說明時對齊的
//portBYTE_ALIGNMENT_MASK  為0x0007
        if( xWantedSize & portBYTE_ALIGNMENT_MASK )
        {
          //將字節(jié)進行對齊
            xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
        }
    }
    #endif
 
 // 掛起任務(wù)調(diào)度器,因為申請內(nèi)存過程中需要保護,不能被打斷
    vTaskSuspendAll();
    {
        if( pucAlignedHeap == NULL )
        {
            /* 第一次使用,確保內(nèi)存堆起始位置正確對齊 */
//內(nèi)存堆ucheap:由編譯器分配,不一定為8字節(jié)對齊的。
            pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
        }

        /* 邊界檢查,檢查可用內(nèi)存是否夠分配,變量xNextFreeByte是局部靜態(tài)變量,初始值為0 */
        if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
            ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )
        {
            /* 返回申請的內(nèi)存起始地址并更新索引 */
            pvReturn = pucAlignedHeap + xNextFreeByte;
//內(nèi)存申請完成后,更新變量xNextFreeByte
            xNextFreeByte += xWantedSize;
        }
    }
    ( void ) xTaskResumeAll();
 
 
    #if( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
            extern void vApplicationMallocFailedHook( void );
            vApplicationMallocFailedHook();
        }
    }
    #endif
 
 // 成功返回內(nèi)存首地址,失敗返回NULL
    return pvReturn;
}

1.2 heap_2.c

heap_2.c適用于需要動態(tài)創(chuàng)建任務(wù)的大多數(shù)小型實時系統(tǒng)(smallreal time)。
和方案1不同,這個方案使用一個最佳匹配算法,它允許釋放之前分配的內(nèi)存塊,但是它不會把相鄰的空閑塊合成一個更大的塊(換句話說,這會造成內(nèi)存碎片)。

有效的堆??臻g大小由位于FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE宏來定義。
為了實現(xiàn)內(nèi)存釋放,heap_2引入內(nèi)存塊的概念,每分出去一段內(nèi)存就是一個內(nèi)存塊,剩下的空閑內(nèi)存也是一個內(nèi)存塊。

//使用一個鏈表結(jié)構(gòu)來跟蹤記錄空閑內(nèi)存塊,將空閑塊組成一個鏈表。8個字節(jié)
typedef struct A_BLOCK_LINK
{
    struct A_BLOCK_LINK *pxNextFreeBlock;   /*指向列表中下一個空閑塊*/
    size_t xBlockSize;                      /*當(dāng)前空閑塊的大小,包括鏈表結(jié)構(gòu)大小*/
} BlockLink_t;

為了方便管理,可用的內(nèi)存塊會被組織在一個鏈表里,局部靜態(tài)變量xStart, xEnd用來記錄這個鏈表的頭尾
static BlockLink_t xStart, xEnd;

每個內(nèi)存塊前面都有BlockLink_t類型變量來描述此內(nèi)存塊。如下圖:

  • 不能用在分配和釋放隨機字節(jié)堆??臻g的應(yīng)用程序
    如果一個應(yīng)用程序動態(tài)的創(chuàng)建和刪除任務(wù),并且分配給任務(wù)的堆??臻g總是同樣大小,那么大多數(shù)情況下heap_2.c是可以使用的。但是,如果分配給任務(wù)的堆棧不總是相等,那么釋放的有效內(nèi)存可能碎片化,形成很多小的內(nèi)存塊。最后可能剩余很多個很小的空閑塊。(如下圖)最后會因為沒有足夠大的連續(xù)堆??臻g而造成內(nèi)存分配失敗。在這種情況下,heap_4.c是一個很好的選擇。
  • 應(yīng)用程序直接調(diào)用pvPortMalloc() 和 vPortFree()函數(shù),而不僅是通過FreeRTOS API間接調(diào)用。

pvPortMalloc()申請內(nèi)存

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void *pvReturn = NULL; 
 
    /* 掛起調(diào)度器 */
    vTaskSuspendAll();
    {
        /* 如果是第一次調(diào)用內(nèi)存分配函數(shù),這里先初始化內(nèi)存堆,如圖2-2所示 */
        if( xHeapHasBeenInitialised == pdFALSE )
        {
            prvHeapInit();//完成內(nèi)存堆的對齊,xstart和xEnd的初始化
            xHeapHasBeenInitialised = pdTRUE;
        }
 
 
        /* 調(diào)整要分配的內(nèi)存值,需要增加上鏈表結(jié)構(gòu)體空間,heapSTRUCT_SIZE表示經(jīng)過對齊擴展后的結(jié)構(gòu)體大小 */
//即實際申請的內(nèi)存需要加上BlockLink_t的大小
        if( xWantedSize > 0 )
        {
            xWantedSize += heapSTRUCT_SIZE;
  
            /* 調(diào)整實際分配的內(nèi)存大小,向上擴大到對齊字節(jié)數(shù)的整數(shù)倍 */
            if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
            {
                xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
            }
        }
        
        if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )
        {
            /* 空閑內(nèi)存塊是按照塊的大小排序的,從鏈表頭xStart開始,小的在前大的在后,以鏈表尾xEnd結(jié)束 */
            pxPreviousBlock = &xStart;
            pxBlock = xStart.pxNextFreeBlock;
            /* 搜索最合適的空閑塊 */
            while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
            {
                pxPreviousBlock = pxBlock;
                pxBlock = pxBlock->pxNextFreeBlock;
            } 
 
            /* 如果搜索到鏈表尾xEnd,說明沒有找到合適的空閑內(nèi)存塊,否則進行下一步處理 */
            if( pxBlock != &xEnd )
            {
                /* 返回內(nèi)存空間,注意是跳過了結(jié)構(gòu)體BlockLink_t空間. */
                pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
 
 
                /* 這個塊就要返回給用戶,因此它必須從空閑塊中去除. */
                pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
 
 
                /* 如果這個塊剩余的空間足夠多,則將它分成兩個,第一個返回給用戶,第二個作為新的空閑塊插入到空閑塊列表中去*/
                if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
                {
                    /* 去除分配出去的內(nèi)存,在剩余內(nèi)存塊的起始位置放置一個鏈表結(jié)構(gòu)并初始化鏈表成員 */
                    pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
 
 
                    pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
                    pxBlock->xBlockSize = xWantedSize;
 
 
                    /* 將剩余的空閑塊插入到空閑塊列表中,按照空閑塊的大小順序,小的在前大的在后 */
                    prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
                }
                /* 計算未分配的內(nèi)存堆大小,注意這里并不能包含內(nèi)存碎片信息 */
                xFreeBytesRemaining -= pxBlock->xBlockSize;
            }
        }
 
 
        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();
 
 
    #if( configUSE_MALLOC_FAILED_HOOK == 1 )
    {   /* 如果內(nèi)存分配失敗,調(diào)用鉤子函數(shù) */
        if( pvReturn == NULL )
        {
            extern void vApplicationMallocFailedHook( void );
            vApplicationMallocFailedHook();
        }
    }
    #endif
 
 
    return pvReturn;
}

釋放內(nèi)存:vPortFree()

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
 
 
    if( pv != NULL )
    {
//puc:要釋放內(nèi)存的首地址,是pvReturn所指向的地址
        /* 根據(jù)傳入的參數(shù)找到鏈表結(jié)構(gòu) */
        puc -= heapSTRUCT_SIZE;
 
        /* 預(yù)防某些編譯器警告 */
        pxLink = ( void * ) puc; 
        vTaskSuspendAll();
        {
            /* 將這個塊添加到空閑塊列表 */
            prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
            /* 更新未分配的內(nèi)存堆大小 */
            xFreeBytesRemaining += pxLink->xBlockSize;
            
            traceFREE( pv, pxLink->xBlockSize );
        }
        ( void ) xTaskResumeAll();
    }
}

1.3 heap_3.c

heap_3.c簡單的包裝了標(biāo)準(zhǔn)庫中的malloc()和free()函數(shù),包裝后的malloc()和free()函數(shù)具備線程保護。
因此,內(nèi)存堆需要通過編譯器或者啟動文件設(shè)置堆空間。
功能:

  • 第一種和第二種內(nèi)存管理策略都是通過定義一個大數(shù)組作為內(nèi)存堆,數(shù)組的大小由宏configTOTAL_HEAP_SIZE指定。需要編譯器設(shè)置一個堆??臻g,一般在啟動代碼中設(shè)置,并且編譯器庫提供malloc()和free()函數(shù)。
  • 不具有確定性
  • 可能明顯的增大RTOS內(nèi)核的代碼大小
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn;
 
 
    vTaskSuspendAll();
    {
        pvReturn = malloc( xWantedSize );
        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();
 
 
    #if( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
            extern void vApplicationMallocFailedHook( void );
            vApplicationMallocFailedHook();
        }
    }
    #endif
 
 
    return pvReturn;
}

1.4 heap_4.c

heap_4.c還特別適用于移植層代碼,可以直接使用pvPortMalloc()和 vPortFree()函數(shù)來分配和釋放內(nèi)存.
這個方案使用一個最佳匹配算法,但不像方案2那樣。它會將相鄰的空閑內(nèi)存塊合并成一個更大的塊(包含一個合并算法)。
與heap_2不同:

  • 鏈表尾保存在內(nèi)存堆空間的最后位置,pxEnd,第二種是xEnd指向鏈表尾
  • 第四種小地址在前,大地址在后(為了適應(yīng)合并算法),而第二種是小內(nèi)存在前。

API函數(shù)xPortGetFreeHeapSize()返回剩下的未分配堆??臻g的大?。捎糜趦?yōu)化設(shè)置configTOTAL_HEAP_SIZE宏的值),但是不能提供未分配內(nèi)存的碎片細(xì)節(jié)信息。

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL; 
 
    vTaskSuspendAll();
    {
        /* 如果是第一次調(diào)用內(nèi)存分配函數(shù),則初始化內(nèi)存堆 */
        if( pxEnd == NULL )
        {
            prvHeapInit();
        }
  
        /* 申請的內(nèi)存大小合法性檢查:是否過大.結(jié)構(gòu)體BlockLink_t中有一個成員xBlockSize表示塊的大小,這個成員的最高位被用來標(biāo)識這個塊是否空閑.因此要申請的塊大小,不能使用這個位.*/
        if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
        {
            /* 計算實際要分配的內(nèi)存大小,包含鏈接結(jié)構(gòu)體BlockLink_t在內(nèi),并且要向上字節(jié)對齊 */
            if( xWantedSize > 0 )
            {
                xWantedSize += xHeapStructSize; 
 
                /* 對齊操作,向上擴大到對齊字節(jié)數(shù)的整數(shù)倍 */
                if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
                {
                    xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
                    configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
                }
            }
 
 
            if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
            {
                /* 從鏈表xStart開始查找,從空閑塊鏈表(按照空閑塊地址順序排列)中找出一個足夠大的空閑塊 */
                pxPreviousBlock = &xStart;//上一個塊是xStart
                pxBlock = xStart.pxNextFreeBlock;//將要判斷的塊
//遍歷可用空間,直到內(nèi)存大小滿足想要申請的。
                while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
                {
                    pxPreviousBlock = pxBlock;// pxPreviousBlock的下一個內(nèi)存塊就是找到的可用內(nèi)存塊
                    pxBlock = pxBlock->pxNextFreeBlock;
                }
 
 
                /* 如果最后到達(dá)結(jié)束標(biāo)識,則說明沒有合適的內(nèi)存塊,否則,進行內(nèi)存分配操作*/
                if( pxBlock != pxEnd )
                {
                    /* 返回分配的內(nèi)存指針,要跳過內(nèi)存開始處的BlockLink_t結(jié)構(gòu)體 */
                    pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
 
 
                    /* 將已經(jīng)分配出去的內(nèi)存塊從空閑塊鏈表中刪除 */
                    pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
 
 
                    /* 如果剩下的內(nèi)存足夠大,則組成一個新的空閑塊 */
                    if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
                    {
                        /* 在剩余內(nèi)存塊的起始位置放置一個鏈表結(jié)構(gòu)并初始化鏈表成員 */
                        pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
                        configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
 
 
                        pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
                        pxBlock->xBlockSize = xWantedSize;
 
 
                        /* 將剩余的空閑塊插入到空閑塊列表中,按照空閑塊的地址大小順序,地址小的在前,地址大的在后 */
                        prvInsertBlockIntoFreeList( pxNewBlockLink );
                    }
                    
                    /* 計算未分配的內(nèi)存堆空間,注意這里并不能包含內(nèi)存碎片信息 */
                    xFreeBytesRemaining -= pxBlock->xBlockSize;
                    
                    /* 保存未分配內(nèi)存堆空間歷史最小值 */
                    if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
                    {
                        xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
                    }
 
 
                    /* 將已經(jīng)分配的內(nèi)存塊標(biāo)識為"已分配" */
                    pxBlock->xBlockSize |= xBlockAllocatedBit;
                    pxBlock->pxNextFreeBlock = NULL;
                }
            }
        }
 
 
        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();
 
 
    #if( configUSE_MALLOC_FAILED_HOOK == 1 )
    {   /* 如果內(nèi)存分配失敗,調(diào)用鉤子函數(shù) */
        if( pvReturn == NULL )
        {
            extern void vApplicationMallocFailedHook( void );
            vApplicationMallocFailedHook();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif
 
 
    configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
    return pvReturn;
}

1.5 heap_5.c

Heap_5通過調(diào)用vPortDefineHeapRegions()函數(shù)實現(xiàn)初始化,它允許程序設(shè)置多個非連續(xù)內(nèi)存堆,如片內(nèi)RAM和片外RAM。使用heap_5創(chuàng)建任何對象前,要先執(zhí)行vPortDefineHeapRegions()函數(shù)。
創(chuàng)建RTOS對象(任務(wù)、隊列、信號量等等)會隱含的調(diào)用pvPortMalloc().

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容