亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

FreeRTOS進(jìn)階內(nèi)存管理示例完全解析

 更新時(shí)間:2022年04月08日 17:56:16   作者:zhzht19861011  
這篇文章主要為大家介紹了FreeRTOS進(jìn)階內(nèi)存管理示例的完全解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪

前言

內(nèi)存管理對(duì)應(yīng)用程序和操作系統(tǒng)來說都非常重要。現(xiàn)在很多的程序漏洞和運(yùn)行崩潰都和內(nèi)存分配使用錯(cuò)誤有關(guān)。

FreeRTOS操作系統(tǒng)將內(nèi)核與內(nèi)存管理分開實(shí)現(xiàn),操作系統(tǒng)內(nèi)核僅規(guī)定了必要的內(nèi)存管理函數(shù)原型,而不關(guān)心這些內(nèi)存管理函數(shù)是如何實(shí)現(xiàn)的。這樣做大有好處,可以增加系統(tǒng)的靈活性:不同的應(yīng)用場合可以使用不同的內(nèi)存分配實(shí)現(xiàn),選擇對(duì)自己更有利的內(nèi)存管理策略。比如對(duì)于安全型的嵌入式系統(tǒng),通常不允許動(dòng)態(tài)內(nèi)存分配,那么可以采用非常簡單的內(nèi)存管理策略,一經(jīng)申請(qǐng)的內(nèi)存,甚至不允許被釋放。在滿足設(shè)計(jì)要求的前提下,系統(tǒng)越簡單越容易做的更安全。再比如一些復(fù)雜應(yīng)用,要求動(dòng)態(tài)的申請(qǐng)、釋放內(nèi)存操作,那么也可以設(shè)計(jì)出相對(duì)復(fù)雜的內(nèi)存管理策略,允許動(dòng)態(tài)分配和動(dòng)態(tài)釋放。

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

void *pvPortMalloc( size_t xSize ) :內(nèi)存申請(qǐng)函數(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)存堆歷史最小值

FreeRTOS提供了5種內(nèi)存管理實(shí)現(xiàn),有簡單也有復(fù)雜的,可以應(yīng)用于絕大多數(shù)場合。它們位于下載包目錄...\FreeRTOS\Source\portable\MemMang中,文件名分別為:heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c。我在FreeRTOS進(jìn)階內(nèi)存管理這篇文章中介紹了這5種內(nèi)存管理的特性以及各自應(yīng)用的場合,今天我們要分析它們的實(shí)現(xiàn)方法。

FreeRTOS提供的內(nèi)存管理都是從內(nèi)存堆中分配內(nèi)存的。默認(rèn)情況下,F(xiàn)reeRTOS內(nèi)核創(chuàng)建任務(wù)、隊(duì)列、信號(hào)量、事件組、軟件定時(shí)器都是借助內(nèi)存管理函數(shù)從內(nèi)存堆中分配內(nèi)存。最新的FreeRTOS版本(V9.0.0及其以上版本)可以完全使用靜態(tài)內(nèi)存分配方法,也就是不使用任何內(nèi)存堆。

對(duì)于heap_1.c、heap_2.c和heap_4.c這三種內(nèi)存管理策略,內(nèi)存堆實(shí)際上是一個(gè)很大的數(shù)組,定義為:

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

其中宏configTOTAL_HEAP_SIZE用來定義內(nèi)存堆的大小,這個(gè)宏在FreeRTOSConfig.h中設(shè)置。

對(duì)于heap_3.c,這種策略只是簡單的包裝了標(biāo)準(zhǔn)庫中的malloc()和free()函數(shù),包裝后的malloc()和free()函數(shù)具備線程保護(hù)。因此,內(nèi)存堆需要通過編譯器或者啟動(dòng)文件設(shè)置堆空間。

heap_5.c比較有趣,它允許程序設(shè)置多個(gè)非連續(xù)內(nèi)存堆,比如需要快速訪問的內(nèi)存堆設(shè)置在片內(nèi)RAM,稍微慢速訪問的內(nèi)存堆設(shè)置在外部RAM。每個(gè)內(nèi)存堆的起始地址和大小由應(yīng)用程序設(shè)計(jì)者定義。

1. heap_1.c

這是5個(gè)內(nèi)存管理策略中最簡單的一個(gè),我們稱為第一個(gè)內(nèi)存管理策略,它簡單到只能申請(qǐng)內(nèi)存。是的,跟你想的一樣,一旦申請(qǐng)成功后,這塊內(nèi)存再也不能被釋放。對(duì)于大多數(shù)嵌入式系統(tǒng),特別是對(duì)安全要求高的嵌入式系統(tǒng),這種內(nèi)存管理策略很有用,因?yàn)閷?duì)系統(tǒng)軟件來說,邏輯越簡單越容易兼顧安全。實(shí)際上,大多數(shù)的嵌入式系統(tǒng)并不需要?jiǎng)討B(tài)刪除任務(wù)、信號(hào)量、隊(duì)列等,而是在初始化的時(shí)候一次性創(chuàng)建好,便一直使用,永遠(yuǎn)不用刪除。所以這個(gè)內(nèi)存管理策略實(shí)現(xiàn)簡潔、安全可靠,使用的非常廣泛。我對(duì)這個(gè)對(duì)內(nèi)存管理策略也情有獨(dú)鐘。

我們可以將第一種內(nèi)存管理看作是切面包:初始化的內(nèi)存就像一根完整的長棍面包,每次申請(qǐng)內(nèi)存,就從一端切下適當(dāng)長度的面包返還給申請(qǐng)者,直到面包被分配完畢,就這么簡單。
這個(gè)內(nèi)存管理策略使用兩個(gè)局部靜態(tài)變量來跟蹤內(nèi)存分配,變量定義為:

static size_t xNextFreeByte = ( size_t ) 0;
static uint8_t *pucAlignedHeap = NULL;

其中,變量xNextFreeByte記錄已經(jīng)分配的內(nèi)存大小,用來定位下一個(gè)空閑的內(nèi)存堆位置。因?yàn)閮?nèi)存堆實(shí)際上是一個(gè)大數(shù)組,我們只需要知道已分配內(nèi)存的大小,就可以用它作為偏移量找到未分配內(nèi)存的起始地址。變量xNextFreeByte被初始化為0,然后每次申請(qǐng)內(nèi)存成功后,都會(huì)增加申請(qǐng)內(nèi)存的字節(jié)數(shù)目。
變量pucAlignedHeap指向?qū)R后的內(nèi)存堆起始位置。為什么要對(duì)齊?這是因?yàn)榇蠖鄶?shù)硬件訪問內(nèi)存對(duì)齊的數(shù)據(jù)速度會(huì)更快。為了提高性能,F(xiàn)reeRTOS會(huì)進(jìn)行對(duì)齊操作,不同的硬件架構(gòu)對(duì)齊操作也不盡相同,對(duì)于Cortex-M3架構(gòu),進(jìn)行8字節(jié)對(duì)齊。
我們來看一下第一種內(nèi)存管理策略對(duì)外提供的API函數(shù)。

1.1內(nèi)存申請(qǐng):pvPortMalloc() 函數(shù)源碼為:

void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;
     /* 確保申請(qǐng)的字節(jié)數(shù)是對(duì)齊字節(jié)數(shù)的倍數(shù) */
    #if( portBYTE_ALIGNMENT != 1 )
    {
        if( xWantedSize & portBYTE_ALIGNMENT_MASK )
        {
            xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
        }
    }
    #endif
    vTaskSuspendAll();
    {
        if( pucAlignedHeap == NULL )
        {
            /* 第一次使用,確保內(nèi)存堆起始位置正確對(duì)齊 */
            pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
        }
        /* 邊界檢查,變量xNextFreeByte是局部靜態(tài)變量,初始值為0 */
        if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
            ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )
        {
            /* 返回申請(qǐng)的內(nèi)存起始地址并更新索引 */
            pvReturn = pucAlignedHeap + xNextFreeByte;
            xNextFreeByte += xWantedSize;
        }
    }
    ( void ) xTaskResumeAll();
    #if( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
            extern void vApplicationMallocFailedHook( void );
            vApplicationMallocFailedHook();
        }
    }
    #endif
    return pvReturn;
}

函數(shù)一開始會(huì)將申請(qǐng)的內(nèi)存數(shù)量調(diào)整到對(duì)齊字節(jié)數(shù)的整數(shù)倍,所以實(shí)際分配的內(nèi)存空間可能比申請(qǐng)內(nèi)存大。比如對(duì)于8字節(jié)對(duì)齊的系統(tǒng),申請(qǐng)11字節(jié)內(nèi)存,經(jīng)過對(duì)齊后,實(shí)際分配的內(nèi)存是16字節(jié)(8的整數(shù)倍)。

接下來會(huì)掛起所有任務(wù),因?yàn)閮?nèi)存申請(qǐng)是不可重入的(使用了靜態(tài)變量)。
如果是第一次執(zhí)行這個(gè)函數(shù),需要將變量pucAlignedHeap指向內(nèi)存堆區(qū)域第一個(gè)地址對(duì)齊處。我們上面說內(nèi)存堆其實(shí)是一個(gè)大數(shù)組,編譯器為這個(gè)數(shù)組分配的起始地址是隨機(jī)的,可能不符合我們的對(duì)齊需要,這時(shí)候要進(jìn)行調(diào)整。比如內(nèi)存堆數(shù)組ucHeap從RAM地址0x10002003處開始,系統(tǒng)按照8字節(jié)對(duì)齊,則對(duì)齊后的內(nèi)存堆如圖1-1所示:

圖1-1:內(nèi)存堆大小與地址對(duì)齊示意圖

之后進(jìn)行邊界檢查,查看剩余的內(nèi)存堆是否夠分配,檢查xNextFreeByte + xWantedSize是否溢出。如果檢查通過,則為申請(qǐng)者返回有效的內(nèi)存指針并更新已分配內(nèi)存數(shù)量計(jì)數(shù)器xNextFreeByte(從指針pucAlignedHeap開始,偏移量為xNextFreeByte處的內(nèi)存區(qū)域?yàn)槲捶峙涞膬?nèi)存堆起始位置)。比如我們首次調(diào)用內(nèi)存分配函數(shù)pvPortMalloc(20),申請(qǐng)20字節(jié)內(nèi)存。根據(jù)對(duì)齊原則,我們會(huì)實(shí)際申請(qǐng)到24字節(jié)內(nèi)存,申請(qǐng)成功后,內(nèi)存堆示意圖如圖1-2所示。

圖1-2:第一次分配內(nèi)存后的內(nèi)存堆空間示意圖

內(nèi)存分配完成后,不管有沒有分配成功都恢復(fù)之前掛起的調(diào)度器。
如果內(nèi)存分配不成功,這里最可能是內(nèi)存堆空間不夠用了,會(huì)調(diào)用一個(gè)鉤子函數(shù)vApplicationMallocFailedHook()。這個(gè)鉤子函數(shù)由應(yīng)用程序提供,通常我們可以打印內(nèi)存分配設(shè)備信息或者點(diǎn)亮也故障指示燈。

1.2獲取當(dāng)前未分配的內(nèi)存堆大?。簒PortGetFreeHeapSize()

函數(shù)用于返回未分配的內(nèi)存堆大小。這個(gè)函數(shù)也很有用,通常用于檢查我們?cè)O(shè)置的內(nèi)存堆是否合理,通過這個(gè)函數(shù)我們可以估計(jì)出最壞情況下需要多大的內(nèi)存堆,以便合理的節(jié)省RAM。
對(duì)于第一個(gè)內(nèi)存管理策略,這個(gè)函數(shù)實(shí)現(xiàn)十分簡單,源碼如下:

size_t xPortGetFreeHeapSize( void )
{
    return ( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}

從圖1-1和圖1-2我們知道,宏configADJUSTED_HEAP_SIZE表示內(nèi)存堆有效的大小,這個(gè)值減去已經(jīng)分配出去的內(nèi)存大小,正是我們需要的未分配的內(nèi)存堆大小。

1.3其它函數(shù)

第一個(gè)內(nèi)存管理策略中還有兩個(gè)函數(shù):vPortFree()和vPortInitialiseBlocks()。但實(shí)際上第一個(gè)函數(shù)什么也不做;第二個(gè)函數(shù)僅僅將靜態(tài)局部變量xNextFreeByte設(shè)置為0。

2. heap_2.c

第二種內(nèi)存管理策略要比第一種內(nèi)存管理策略復(fù)雜,它使用一個(gè)最佳匹配算法,允許釋放之前已分配的內(nèi)存塊,但是它不會(huì)把相鄰的空閑塊合成一個(gè)更大的塊(換句話說,這會(huì)造成內(nèi)存碎片)。

這個(gè)內(nèi)存管理策略用于重復(fù)的分配和刪除具有相同堆??臻g的任務(wù)、隊(duì)列、信號(hào)量、互斥量等等,并且不考慮內(nèi)存碎片的應(yīng)用程序,不適用于分配和釋放隨機(jī)字節(jié)堆??臻g的應(yīng)用程序!

與第一種內(nèi)存管理策略一樣,內(nèi)存堆仍然是一個(gè)大數(shù)組,定義為:

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

局部靜態(tài)變量pucAlignedHeap指向?qū)R后的內(nèi)存堆起始位置。地址對(duì)齊的原因在第一種內(nèi)存管理策略中已經(jīng)說明。假如內(nèi)存堆數(shù)組ucHeap從RAM地址0x10002003處開始,系統(tǒng)按照8字節(jié)對(duì)齊,則對(duì)齊后的內(nèi)存堆與第一個(gè)內(nèi)存管理策略一樣,如圖2-1所示:

圖2-1:內(nèi)存堆示大小與地址對(duì)齊示意圖

2.1內(nèi)存申請(qǐng):pvPortMalloc()

與第一種內(nèi)存管理策略不同,第二種內(nèi)存管理策略使用一個(gè)鏈表結(jié)構(gòu)來跟蹤記錄空閑內(nèi)存塊,將空閑塊組成一個(gè)鏈表。結(jié)構(gòu)體定義為:

typedef struct A_BLOCK_LINK
{
    struct A_BLOCK_LINK *pxNextFreeBlock;   /*指向列表中下一個(gè)空閑塊*/
    size_t xBlockSize;                      /*當(dāng)前空閑塊的大小,包括鏈表結(jié)構(gòu)大小*/
} BlockLink_t;

兩個(gè)BlockLink_t類型的局部靜態(tài)變量xStart和xEnd用來標(biāo)識(shí)空閑內(nèi)存塊的起始和結(jié)束。剛開始時(shí),整個(gè)內(nèi)存堆有效空間就是一個(gè)空閑塊,如圖2-2所示。因?yàn)橐男畔⒃絹碓蕉?,我們必須舍棄一些信息,舍棄的信息可以在上一幅圖中找到。

圖2-2:內(nèi)存堆初始化示意圖

圖2-2中的pvReturn是我自己增加的,用于接下來分析內(nèi)存申請(qǐng)操作,堆棧初始化并沒有這個(gè)變量,也沒有對(duì)其操作的代碼。從圖2-2中可以看出,整個(gè)有效空間組成唯一一個(gè)空閑塊,在空閑塊的起始位置放置了一個(gè)鏈表結(jié)構(gòu),用于存儲(chǔ)這個(gè)空閑塊的大小和下一個(gè)空閑塊的地址。由于目前只有一個(gè)空閑塊,所以空閑塊的pxNextFreeBlock指向鏈表xEnd,而鏈表xStart結(jié)構(gòu)的pxNextFreeBlock指向空閑塊。這樣,xStart、空閑塊和xEnd組成一個(gè)單鏈表,xStart表示鏈表頭,xEnd表示鏈表尾。隨著內(nèi)存申請(qǐng)和釋放,空閑塊可能會(huì)越來越多,但它們?nèi)允且詘Start鏈表開頭以xEnd鏈表結(jié)尾,根據(jù)空閑塊的大小排序,小的在前,大的在后,我們?cè)趦?nèi)存釋放一節(jié)中會(huì)給出示意圖。

當(dāng)申請(qǐng)N字節(jié)內(nèi)存時(shí),實(shí)際上不僅需要分配N字節(jié)內(nèi)存,還要分配一個(gè)BlockLink_t類型結(jié)構(gòu)體空間,用于描述這個(gè)內(nèi)存塊,結(jié)構(gòu)體空間位于空閑內(nèi)存塊的最開始處。當(dāng)然,和第一種內(nèi)存管理策略一樣,申請(qǐng)的內(nèi)存大小和BlockLink_t類型結(jié)構(gòu)體大小都要向上擴(kuò)大到對(duì)齊字節(jié)數(shù)的整數(shù)倍。

我們看一下內(nèi)存申請(qǐng)過程:首先計(jì)算實(shí)際要分配的內(nèi)存大小,判斷申請(qǐng)的內(nèi)存是否合法。如果合法則從鏈表頭xStart開始查找,如果某個(gè)空閑塊的xBlockSize字段大小能容得下要申請(qǐng)的內(nèi)存,則從這塊內(nèi)存取出合適的部分返回給申請(qǐng)者,剩下的內(nèi)存塊組成一個(gè)新的空閑塊,按照空閑塊的大小順序插入到空閑塊鏈表中,小塊在前大塊在后。注意,返回的內(nèi)存中不包括鏈表結(jié)構(gòu),而是緊鄰鏈表結(jié)構(gòu)(經(jīng)過對(duì)齊)后面的位置。舉個(gè)例子,如圖2-2所示的內(nèi)存堆,當(dāng)調(diào)用申請(qǐng)內(nèi)存函數(shù),如果內(nèi)存堆空間足夠大,就將pvReturn指向的地址返回給申請(qǐng)者,而不是靜態(tài)變量pucAlignedHeap指向的內(nèi)存堆起始位置!

當(dāng)多次調(diào)用內(nèi)存申請(qǐng)函數(shù)后(沒有調(diào)用內(nèi)存釋放函數(shù)),內(nèi)存堆結(jié)構(gòu)如圖2-3所示。注意圖中的pvReturn仍是我自己增加上去的,pvReturn指向的位置返回給申請(qǐng)者。后面我們講內(nèi)存釋放時(shí),就是根據(jù)這個(gè)地址完成內(nèi)存釋放工作的。

圖2-3:經(jīng)過兩次內(nèi)存分配后的內(nèi)存堆示意圖

有了上面的這些基礎(chǔ)知識(shí),再看內(nèi)存申請(qǐng)函數(shù)源碼就比較簡單了,我把需要注意的要點(diǎn)以注釋的方式放在源碼中,不再單獨(dú)對(duì)這個(gè)函數(shù)做講解,值得注意的是函數(shù)中使用的一個(gè)靜態(tài)局部變量xFreeBytesRemaining,它用來記錄未分配的內(nèi)存堆大小。這個(gè)變量將提供給函數(shù)xPortGetFreeHeapSize()使用,以方便用戶估算內(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();
            xHeapHasBeenInitialised = pdTRUE;
        }
        /* 調(diào)整要分配的內(nèi)存值,需要增加上鏈表結(jié)構(gòu)體空間,heapSTRUCT_SIZE表示經(jīng)過對(duì)齊擴(kuò)展后的結(jié)構(gòu)體大小 */
        if( xWantedSize > 0 )
        {
            xWantedSize += heapSTRUCT_SIZE;
            /* 調(diào)整實(shí)際分配的內(nèi)存大小,向上擴(kuò)大到對(duì)齊字節(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)存塊,否則進(jìn)行下一步處理 */
            if( pxBlock != &xEnd )
            {
                /* 返回內(nèi)存空間,注意是跳過了結(jié)構(gòu)體BlockLink_t空間. */
                pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
                /* 這個(gè)塊就要返回給用戶,因此它必須從空閑塊中去除. */
                pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
                /* 如果這個(gè)塊剩余的空間足夠多,則將它分成兩個(gè),第一個(gè)返回給用戶,第二個(gè)作為新的空閑塊插入到空閑塊列表中去*/
                if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
                {
                    /* 去除分配出去的內(nèi)存,在剩余內(nèi)存塊的起始位置放置一個(gè)鏈表結(jié)構(gòu)并初始化鏈表成員 */
                    pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
                    pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
                    pxBlock->xBlockSize = xWantedSize;
                   /* 將剩余的空閑塊插入到空閑塊列表中,按照空閑塊的大小順序,小的在前大的在后 */
                    prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
                }
                /* 計(jì)算未分配的內(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;
}

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

因?yàn)椴恍枰喜⑾噜彽目臻e塊,第二種內(nèi)存管理策略的內(nèi)存釋放也非常簡單:根據(jù)傳入的參數(shù)找到鏈表結(jié)構(gòu),然后將這個(gè)內(nèi)存塊插入到空閑塊列表,更新未分配的內(nèi)存堆計(jì)數(shù)器大小,結(jié)束。因?yàn)楹唵危覀冎苯涌丛创a。

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

我們舉一個(gè)例子,將圖2-3 pvReturn指向的內(nèi)存塊釋放掉,假設(shè)(configADJUSTED_HEAP_SIZE-40)遠(yuǎn)大于要釋放的內(nèi)存塊大小,釋放后的內(nèi)存堆如圖2-4所示:

圖2-4:釋放內(nèi)存后,內(nèi)存堆示意圖

從圖2-4我們可以看出第二種內(nèi)存管理策略的兩個(gè)特點(diǎn):第一,空閑塊是按照大小排序的;第二,相鄰的空閑塊不會(huì)組合成一個(gè)大塊。

我們?cè)俳又暧懻撘幌逻@種內(nèi)存管理策略的優(yōu)缺點(diǎn)。通過對(duì)內(nèi)存申請(qǐng)和釋放函數(shù)源碼分析,我們可以看出它的一個(gè)優(yōu)點(diǎn)是速度足夠快,因?yàn)樗膶?shí)現(xiàn)非常簡單;第二個(gè)優(yōu)點(diǎn)是可以動(dòng)態(tài)釋放內(nèi)存。但是它的缺點(diǎn)也非常明顯:由于在釋放內(nèi)存時(shí)不會(huì)將相鄰的內(nèi)存塊合并,所以這可能造成內(nèi)存碎片。這就對(duì)其應(yīng)用的場合要求極其苛刻:第一,每次創(chuàng)建或釋放的任務(wù)、信號(hào)量、隊(duì)列等必須大小相同,如果分配或釋放的內(nèi)存是隨機(jī)的,絕對(duì)不可以用這種內(nèi)存管理策略;第二,如果申請(qǐng)和釋放的順序不可預(yù)料,也很危險(xiǎn)。舉個(gè)例子,對(duì)于一個(gè)已經(jīng)初始化的10KB內(nèi)存堆,先申請(qǐng)48字節(jié)內(nèi)存,然后釋放;再接著申請(qǐng)32字節(jié)內(nèi)存,那么一個(gè)本來48字節(jié)的大塊就會(huì)被分為32字節(jié)和16字節(jié)的小塊,如果這種情況經(jīng)常發(fā)生,就會(huì)導(dǎo)致每個(gè)空閑塊都可能很小,最終在申請(qǐng)一個(gè)大塊時(shí)就會(huì)因?yàn)闆]有合適的空閑塊而申請(qǐng)失?。ú⒉皇且?yàn)榭偟目臻e內(nèi)存不足)!

2.3獲取未分配的內(nèi)存堆大?。簒PortGetFreeHeapSize()

函數(shù)用于返回未分配的內(nèi)存堆大小。這個(gè)函數(shù)也很有用,通常用于檢查我們?cè)O(shè)置的內(nèi)存堆是否合理,通過這個(gè)函數(shù)我們可以估計(jì)出最壞情況下需要多大的內(nèi)存堆,以便進(jìn)行合理的節(jié)省RAM。需要注意的是,這個(gè)函數(shù)返回值并不能函數(shù)源碼為:

size_t xPortGetFreeHeapSize( void )
{
    return xFreeBytesRemaining;
}

局部靜態(tài)變量xFreeBytesRemaining在內(nèi)存申請(qǐng)和內(nèi)存釋放函數(shù)中多次提到,它用來動(dòng)態(tài)記錄未分配的內(nèi)存堆大小。

3.heap_3.c

第三種內(nèi)存管理策略簡單的封裝了標(biāo)準(zhǔn)庫中的malloc()和free()函數(shù),采用的封裝方式是操作內(nèi)存前掛起調(diào)度器、完成后再恢復(fù)調(diào)度器。封裝后的malloc()和free()函數(shù)具備線程保護(hù)。

第一種和第二種內(nèi)存管理策略都是通過定義一個(gè)大數(shù)組作為內(nèi)存堆,數(shù)組的大小由宏configTOTAL_HEAP_SIZE指定。第三種內(nèi)存管理策略與前兩種不同,它不再需要通過數(shù)組定義內(nèi)存堆,而是需要使用編譯器設(shè)置內(nèi)存堆空間,一般在啟動(dòng)代碼中設(shè)置。

因此宏configTOTAL_HEAP_SIZE對(duì)這種內(nèi)存管理策略是無效的。

3.1內(nèi)存申請(qǐng):pvPortMalloc()

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;
}

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

void vPortFree( void *pv )
{
    if( pv )
    {
        vTaskSuspendAll();
        {
            free( pv );
            traceFREE( pv, 0 );
        }
        ( void ) xTaskResumeAll();
    }
}

4.heap_4.c

第四種內(nèi)存分配方法與第二種比較相似,只不過增加了一個(gè)合并算法,將相鄰的空閑內(nèi)存塊合并成一個(gè)大塊。

與第一種和第二種內(nèi)存管理策略一樣,內(nèi)存堆仍然是一個(gè)大數(shù)組,定義為:

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

4.1 內(nèi)存申請(qǐng):pvPortMalloc()

和第二種內(nèi)存管理策略一樣,它也使用一個(gè)鏈表結(jié)構(gòu)來跟蹤記錄空閑內(nèi)存塊。結(jié)構(gòu)體定義為:

typedef struct A_BLOCK_LINK
{
    struct A_BLOCK_LINK *pxNextFreeBlock;   /*指向列表中下一個(gè)空閑塊*/
    size_t xBlockSize;                      /*當(dāng)前空閑塊的大小,包括鏈表結(jié)構(gòu)大小*/
} BlockLink_t;

與第二種內(nèi)存管理策略一樣,空閑內(nèi)存塊也是以單鏈表的形式組織起來的,BlockLink_t類型的局部靜態(tài)變量xStart表示鏈表頭,但第四種內(nèi)存管理策略的鏈表尾保存在內(nèi)存堆空間最后位置,并使用BlockLink_t指針類型局部靜態(tài)變量pxEnd指向這個(gè)區(qū)域(第二種內(nèi)存管理策略使用靜態(tài)變量xEnd表示鏈表尾),如圖4-1所示。

第四種內(nèi)存管理策略和第二種內(nèi)存管理策略還有一個(gè)很大的不同是:第四種內(nèi)存管理策略的空閑塊鏈表不是以內(nèi)存塊大小為存儲(chǔ)順序,而是以內(nèi)存塊起始地址大小為存儲(chǔ)順序,地址小的在前,地址大的在后。這也是為了適應(yīng)合并算法而作的改變。

圖4-1:內(nèi)存堆初始化示意圖

從圖4-1中可以看出,整個(gè)有效空間組成唯一一個(gè)空閑塊,在空閑塊的起始位置放置了一個(gè)鏈表結(jié)構(gòu),用于存儲(chǔ)這個(gè)空閑塊的大小和下一個(gè)空閑塊的地址。由于目前只有一個(gè)空閑塊,所以空閑塊的pxNextFreeBlock指向指針pxEnd指向的位置,而鏈表xStart結(jié)構(gòu)的pxNextFreeBlock指向空閑塊。xStart表示鏈表頭,pxEnd指向位置表示鏈表尾。

當(dāng)申請(qǐng)x字節(jié)內(nèi)存時(shí),實(shí)際上不僅需要分配x字節(jié)內(nèi)存,還要分配一個(gè)BlockLink_t類型結(jié)構(gòu)體空間,用于描述這個(gè)內(nèi)存塊,結(jié)構(gòu)體空間位于空閑內(nèi)存塊的最開始處。當(dāng)然,和第一種、第二種內(nèi)存管理策略一樣,申請(qǐng)的內(nèi)存大小和BlockLink_t類型結(jié)構(gòu)體大小都要向上擴(kuò)大到對(duì)齊字節(jié)數(shù)的整數(shù)倍。

我們先說一下內(nèi)存申請(qǐng)過程:首先計(jì)算實(shí)際要分配的內(nèi)存大小,判斷申請(qǐng)內(nèi)存合法性,如果合法則從鏈表頭xStart開始查找,如果某個(gè)空閑塊的xBlockSize字段大小能容得下要申請(qǐng)的內(nèi)存,則將這塊內(nèi)存取出合適的部分返回給申請(qǐng)者,剩下的內(nèi)存塊組成一個(gè)新的空閑塊,按照空閑塊起始地址大小順序插入到空閑塊鏈表中,地址小的在前,地址大的在后。在插入到空閑塊鏈表的過程中,還會(huì)執(zhí)行合并算法:判斷這個(gè)塊是不是可以和上一個(gè)空閑塊合并成一個(gè)大塊,如果可以則合并;然后再判斷能不能和下一個(gè)空閑塊合并成一個(gè)大塊,如果可以則合并!合并算法是第四種內(nèi)存管理策略和第二種內(nèi)存管理策略最大的不同!經(jīng)過幾次內(nèi)存申請(qǐng)和釋放后,可能的內(nèi)存堆如圖4-2所示:

圖4-2:經(jīng)過數(shù)次內(nèi)存申請(qǐng)和釋放后,某個(gè)內(nèi)存堆示意圖

有了上面的基礎(chǔ),我們?cè)賮砜匆幌略创a,我把需要注意的要點(diǎn)以注釋的方式放在源碼中,不再單獨(dú)對(duì)這個(gè)函數(shù)做講解。函數(shù)中會(huì)用到幾個(gè)局部靜態(tài)變量在這里簡單說明一下:

xFreeBytesRemaining:表示當(dāng)前未分配的內(nèi)存堆大小

xMinimumEverFreeBytesRemaining:表示未分配內(nèi)存堆空間歷史最小值。這個(gè)值跟xFreeBytesRemaining有很大區(qū)別,只有記錄未分配內(nèi)存堆的最小值,才能知道最壞情況下內(nèi)存堆的使用情況。

xBlockAllocatedBit:這個(gè)變量在第一次調(diào)用內(nèi)存申請(qǐng)函數(shù)時(shí)被初始化,將它能表示的數(shù)值的最高位置1。比如對(duì)于32位系統(tǒng),這個(gè)變量被初始化為0x80000000(最高位為1)。內(nèi)存管理策略使用這個(gè)變量來標(biāo)識(shí)一個(gè)內(nèi)存塊是否空閑。

如果內(nèi)存塊被分配出去,則內(nèi)存塊鏈表結(jié)構(gòu)成員xBlockSize按位或上這個(gè)變量(即xBlockSize最高位置1),在釋放一個(gè)內(nèi)存塊時(shí),會(huì)把xBlockSize的最高位清零。

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
    vTaskSuspendAll();
    {
        /* 如果是第一次調(diào)用內(nèi)存分配函數(shù),則初始化內(nèi)存堆,初始化后的內(nèi)存堆如圖4-1所示 */
        if( pxEnd == NULL )
        {
            prvHeapInit();
        }
        /* 申請(qǐng)的內(nèi)存大小合法性檢查:是否過大.結(jié)構(gòu)體BlockLink_t中有一個(gè)成員xBlockSize表示塊的大小,這個(gè)成員的最高位被用來標(biāo)識(shí)這個(gè)塊是否空閑.因此要申請(qǐng)的塊大小不能使用這個(gè)位.*/
        if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
        {
            /* 計(jì)算實(shí)際要分配的內(nèi)存大小,包含鏈接結(jié)構(gòu)體BlockLink_t在內(nèi),并且要向上字節(jié)對(duì)齊 */
            if( xWantedSize > 0 )
            {
                xWantedSize += xHeapStructSize;
                /* 對(duì)齊操作,向上擴(kuò)大到對(duì)齊字節(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開始查找,從空閑塊鏈表(按照空閑塊地址順序排列)中找出一個(gè)足夠大的空閑塊 */
                pxPreviousBlock = &xStart;
                pxBlock = xStart.pxNextFreeBlock;
                while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
                {
                    pxPreviousBlock = pxBlock;
                    pxBlock = pxBlock->pxNextFreeBlock;
                }
                /* 如果最后到達(dá)結(jié)束標(biāo)識(shí),則說明沒有合適的內(nèi)存塊,否則,進(jìn)行內(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)存足夠大,則組成一個(gè)新的空閑塊 */
                    if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
                    {
                        /* 在剩余內(nèi)存塊的起始位置放置一個(gè)鏈表結(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 );
                    }
                    /* 計(jì)算未分配的內(nèi)存堆空間,注意這里并不能包含內(nèi)存碎片信息 */
                    xFreeBytesRemaining -= pxBlock->xBlockSize;
                    /* 保存未分配內(nèi)存堆空間歷史最小值 */
                    if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
                    {
                        xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
                    }
                    /* 將已經(jīng)分配的內(nèi)存塊標(biāo)識(shí)為"已分配" */
                    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;
}

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

第四種內(nèi)存管理策略的內(nèi)存釋放也比較簡單:根據(jù)傳入的參數(shù)找到鏈表結(jié)構(gòu),然后將這個(gè)內(nèi)存塊插入到空閑塊列表,需要注意的是在插入過程中會(huì)執(zhí)行合并算法,這個(gè)我們已經(jīng)在內(nèi)存申請(qǐng)中講過了。最后是將這個(gè)內(nèi)存塊標(biāo)志為“空閑”、更新未分配的內(nèi)存堆大小,結(jié)束。源代碼如下:

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
    if( pv != NULL )
    {
        /* 根據(jù)參數(shù)地址找出內(nèi)存塊鏈表結(jié)構(gòu) */
        puc -= xHeapStructSize;
        pxLink = ( void * ) puc;
        /* 檢查這個(gè)內(nèi)存塊確實(shí)被分配出去 */
        if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
        {
            if( pxLink->pxNextFreeBlock == NULL )
            {
                /* 將內(nèi)存塊標(biāo)識(shí)為"空閑" */
                pxLink->xBlockSize &= ~xBlockAllocatedBit;
                 vTaskSuspendAll();
                {
                    /* 更新未分配的內(nèi)存堆大小 */
                    xFreeBytesRemaining += pxLink->xBlockSize;
                    traceFREE( pv, pxLink->xBlockSize );
                    /* 將這個(gè)內(nèi)存塊插入到空閑塊鏈表中,按照內(nèi)存塊地址大小順序 */
                    prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
                }
                ( void ) xTaskResumeAll();
            }
        }
    }
}

如圖4-2所示的內(nèi)存堆示意圖,如果我們將32字節(jié)的“已分配空間2”釋放,由于這個(gè)內(nèi)存塊的上面和下面都是空閑塊,所以在將它插入到空閑塊鏈表的過程在中,會(huì)先和“剩余空閑塊1”合并,合并后的塊再和“剩余空閑塊2”合并,這樣組成一個(gè)大的空閑塊,如圖4-3所示:

圖4-3:內(nèi)存釋放后,會(huì)和相鄰的空閑塊合并

4.3獲取當(dāng)前未分配的內(nèi)存堆大?。簒PortGetFreeHeapSize()

在內(nèi)存申請(qǐng)和內(nèi)存釋放函數(shù)中以及多次提到過變量xFreeBytesRemaining。它就是一個(gè)計(jì)數(shù)器,不能說明內(nèi)存堆碎片信息。

size_t xPortGetFreeHeapSize( void )
{
    return xFreeBytesRemaining;
}

4.4獲取未分配的內(nèi)存堆歷史最小值:xPortGetFreeHeapSize()

在內(nèi)存申請(qǐng)中講解過變量xMinimumEverFreeBytesRemaining,這個(gè)函數(shù)很有用,通過這個(gè)函數(shù)我們可以估計(jì)出最壞情況下需要多大的內(nèi)存堆,從而輔助我們合理的設(shè)置內(nèi)存堆大小。

size_t xPortGetMinimumEverFreeHeapSize( void )
{
    return xMinimumEverFreeBytesRemaining;
}

5.heap_5.c

第五種內(nèi)存管理策略允許內(nèi)存堆跨越多個(gè)非連續(xù)的內(nèi)存區(qū),并且需要顯示的初始化內(nèi)存堆,除此之外其它操作都和第四種內(nèi)存管理策略十分相似。

第一、第二和第四種內(nèi)存管理策略都是利用一個(gè)大數(shù)組作為內(nèi)存堆使用,并且只需要應(yīng)用程序指定數(shù)組的大?。ㄍㄟ^宏configTOTAL_HEAP_SIZE定義),數(shù)組定義由內(nèi)存管理策略實(shí)現(xiàn)。

第五種內(nèi)存管理策略有些不同,首先它允許跨內(nèi)存區(qū)定義多個(gè)內(nèi)存堆,比如在片內(nèi)RAM中定義一個(gè)內(nèi)存堆,還可以在片外RAM再定義內(nèi)存堆;

其次,用戶需要指定每個(gè)內(nèi)存堆區(qū)域的起始地址和內(nèi)存堆大小、將它們放在一個(gè)HeapRegion_t結(jié)構(gòu)體類型數(shù)組中,并需要在使用任何內(nèi)存分配和釋放操作前調(diào)用vPortDefineHeapRegions()函數(shù)初始化這些內(nèi)存堆。

讓我們看一個(gè)例子:假設(shè)我們?yōu)閮?nèi)存堆分配兩個(gè)內(nèi)存塊,第一個(gè)內(nèi)存塊大小為0x10000字節(jié),起始地址為0x80000000;第二個(gè)內(nèi)存塊大小為0xa0000字節(jié),起始地址為0x90000000。HeapRegion_t結(jié)構(gòu)體類型數(shù)組可以定義如下:

HeapRegion_t xHeapRegions[] =
 {
  	{ ( uint8_t * ) 0x80000000UL, 0x10000 }, 
  	{ ( uint8_t * ) 0x90000000UL, 0xa0000 }, 
  	{ NULL, 0 }                
 };

兩個(gè)內(nèi)存塊要按照地址順序放入到數(shù)組中,地址小的在前,因此地址為0x80000000的內(nèi)存塊必須放數(shù)組的第一個(gè)位置。數(shù)組必須以使用一個(gè)NULL指針和0字節(jié)元素作為結(jié)束,以便讓內(nèi)存管理程序知道何時(shí)結(jié)束。

定義好內(nèi)存堆數(shù)組后,需要應(yīng)用程序調(diào)用vPortDefineHeapRegions()函數(shù)初始化這些內(nèi)存堆:將它們組成一個(gè)鏈表,以xStart鏈表結(jié)構(gòu)開頭,以pxEnd指針指向的位置結(jié)束。我們看一下內(nèi)存堆數(shù)組是如何初始化的,以上面的內(nèi)存堆數(shù)組為例,初始化后的內(nèi)存堆如圖5-1所示(32為平臺(tái),sizeof(BlockLink_t)=8字節(jié))。

圖5-1:多個(gè)非連續(xù)內(nèi)存區(qū)用作內(nèi)存堆初始化示意圖

一旦內(nèi)存堆初始化之后,內(nèi)存申請(qǐng)和釋放都和第四種內(nèi)存管理策略相同,不再單獨(dú)分析。

以上就是FreeRTOS進(jìn)階內(nèi)存管理示例完全解析的詳細(xì)內(nèi)容,更多關(guān)于FreeRTOS內(nèi)存管理分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論