FreeRTOS 內(nèi)存 Heap管理

@(嵌入式)

[TOC]

Freertos

FreeRtos

FreeRtos 提供的幾種 heap 管理在源碼目錄 Source/Portable/MemMang 下硅卢,選擇哪種類型管理直接在編譯時(shí)把原文件加入(比如在 makefile SRC中加入)即可释树, 堆大小是 FreeRTOSConfig.h 中的常量 configTOTAL_HEAP_SIZE揣云,默認(rèn)是17*1024银受,即17KB。

FreeRtos 內(nèi)存管理提供的主要接口:

  • pvPortMalloc() 對(duì)應(yīng) malloc()
  • vPortFree() 對(duì)應(yīng) free()
  • xPortGetFreeHeapSize() 獲取剩余可分配內(nèi)存大小

為了適配不同平臺(tái)敛纲、場(chǎng)合需求晌纫,對(duì)接口提供了不同的實(shí)現(xiàn)。

內(nèi)存對(duì)齊

在 portmacro.h (Source/Portable/ + 對(duì)應(yīng)編譯器 + 平臺(tái) 目錄下) 的常量 portBYTE_ALIGNMENT 定義了字節(jié)對(duì)齊境肾,對(duì)應(yīng)的這個(gè)變量決定了 portable.h 中的一個(gè)常量 portBYTE_ALIGNMENT_MASK剔难, 對(duì)應(yīng)關(guān)系如下:

portBYTE_ALIGNMENT portBYTE_ALIGNMENT_MASK
8(表示以8個(gè)字節(jié)對(duì)齊) 0x0007
4(表示以4個(gè)字節(jié)對(duì)齊) 0x0003
2(表示以2個(gè)字節(jié)對(duì)齊) 0x0001
1(表示以1個(gè)字節(jié)對(duì)齊) 0x0000

在堆管理中涉及了一些字節(jié)對(duì)齊,此處做準(zhǔn)備奥喻。

Heap_1

這個(gè)版本的堆管理偶宫,如源碼注釋

The simplest possible implementation of pvPortMalloc(). Note that this implementation does NOT allow allocated memory to be freed again.

實(shí)現(xiàn) pvPortMalloc() 用于內(nèi)存分配,但是不支持回收衫嵌,適用于一些比較小的嵌入式設(shè)備读宙,在系統(tǒng) boot 后申請(qǐng)內(nèi)存運(yùn)行任務(wù),隊(duì)列和信號(hào)量等楔绞,在程序生命期內(nèi)一般沒有釋放的需求结闸。對(duì)于一些安全型的系統(tǒng),一般是不允許動(dòng)態(tài)申請(qǐng)的酒朵,滿足設(shè)計(jì)需求下桦锄,越簡(jiǎn)單越安全。

調(diào)用函數(shù) pvPortMalloc( size_t xWantedSize ) 申請(qǐng)內(nèi)存時(shí)蔫耽,按順序完成如下工作:

  • 字節(jié)對(duì)齊
  • 分配內(nèi)存
  • 調(diào)用鉤子函數(shù)
  • 返回分配內(nèi)存地址

初始化

#define configADJUSTED_HEAP_SIZE    ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
/* Allocate the memory for the heap. */
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
static size_t xNextFreeByte = ( size_t ) 0;

configADJUSTED_HEAP_SIZE 定義了實(shí)際可用的堆大小结耀,因?yàn)楸WC字節(jié)的對(duì)齊,所以減去一個(gè)對(duì)齊的長(zhǎng)度匙铡。
ucHeap[ configTOTAL_HEAP_SIZE ] 和 xNextFreeByte 分別對(duì)應(yīng)堆 的地址和已經(jīng)分配的值图甜,堆實(shí)際上就是一個(gè)靜態(tài)分配的大數(shù)組。

以下代碼鳖眼,均是函數(shù) pvPortMalloc() 的內(nèi)容

字節(jié)對(duì)齊處理

/* Ensure that blocks are always aligned to the required number of bytes. */
#if portBYTE_ALIGNMENT != 1
    if( xWantedSize & portBYTE_ALIGNMENT_MASK )
    {
        /* Byte alignment required. */
        xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
    }
#endif

if 判斷了申請(qǐng)的內(nèi)存大小是否符合字節(jié)對(duì)齊黑毅,如果不符合,則進(jìn)行對(duì)齊處理钦讳。舉個(gè)例子矿瘦,設(shè)置8字節(jié)對(duì)齊,你本來申請(qǐng)的 xWantedSize == 12 個(gè)byte愿卒,與 mask & 的結(jié)果是4(0100B), 不對(duì)齊缚去,為了對(duì)齊,系統(tǒng)會(huì) ”強(qiáng)迫癥“ 多給你4個(gè)字節(jié)琼开。實(shí)際上你不應(yīng)該用到易结,因?yàn)槟闵暾?qǐng)了12bye。

分配內(nèi)存

vTaskSuspendAll();
{
    if( pucAlignedHeap == NULL )
    {
        /* Ensure the heap starts on a correctly aligned boundary. */
        // 字節(jié)對(duì)齊
        pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) 
        &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK ) );
    }

    /* Check there is enough room left for the allocation. */
    // 邊界判斷
    if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
        ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )/* Check for overflow. */
    {
        /* Return the next free byte then increment the index past this
        block. */
        // 切塊蛋糕一樣把內(nèi)存分配出來
        pvReturn = pucAlignedHeap + xNextFreeByte;
        xNextFreeByte += xWantedSize;
    }
    traceMALLOC( pvReturn, xWantedSize );
}   
xTaskResumeAll();
  1. 系統(tǒng)調(diào)用了 vTaskSuspendAll() 掛起所有任務(wù)柜候,保證線程安全搞动, 避免分配時(shí)被切任務(wù)導(dǎo)致出錯(cuò)。
  2. 對(duì)堆的首地址作對(duì)齊處理
    可能有人疑問堆首地址不就是上面提到那個(gè)數(shù)組的首地址 &ucHeap[0]改橘, 為什么這里要使用 &ucHeap[ portBYTE_ALIGNMENT ] & portPOINTER_SIZE_TYPE滋尉?
    其實(shí)這個(gè)地方處理還是考慮對(duì)齊問題,舉個(gè)例子就明白了:
    設(shè)置8byte對(duì)齊飞主, 假如&ucHeap[0]地址是 0x00000006, 為了保證對(duì)齊狮惜,F(xiàn)reeRtos 直接在這個(gè)地址加上一個(gè)對(duì)齊的長(zhǎng)度(保證&后不會(huì)越界訪問),然后和 mark &一下碌识,對(duì)應(yīng)的碾篡,&ucHeap[ portBYTE_ALIGNMENT ]對(duì)應(yīng)了0x000000014,& 上 mark 就是 0x00000008筏餐。由于做了這個(gè)調(diào)整后开泽,實(shí)際的堆大小改變了,所以 configADJUSTED_HEAP_SIZE 表示實(shí)際可用的內(nèi)存大小
  3. 分配內(nèi)存
    Heap_1 比較簡(jiǎn)單魁瞪,按順序分配穆律,所以只需要判斷剩下的內(nèi)存夠大惠呼,直接切出來,更新已分配大小的值峦耘,返回地址就可以了

鉤子函數(shù)調(diào)用&返回地址

定義了configUSE_MALLOC_FAILED_HOOK == 1 后剔蹋, 當(dāng)申請(qǐng)失敗的時(shí)候會(huì)調(diào)用鉤子函數(shù), 也可以自己添加其他處理代碼辅髓。

    #if( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
            extern void vApplicationMallocFailedHook( void );
            vApplicationMallocFailedHook();
        }
    }
    #endif
    return pvReturn;
}

Heap_1 的 vPortFree 函數(shù)就不提了

Heap_2

Heap_2 內(nèi)存分配使用最佳匹配算法(best fit algorithm)泣崩,比如我們申請(qǐng)25k的內(nèi)存,而可申請(qǐng)內(nèi)存中有三塊對(duì)應(yīng)大小30K洛口, 50K 和 100 K矫付,按照最小匹配,這時(shí)候會(huì)把30k進(jìn)行分割并返回申請(qǐng)內(nèi)存的地址第焰,剩余部分插回鏈表留待下次申請(qǐng)买优。
Heap_2 支持內(nèi)存回收,但是不會(huì)把碎片合并樟遣,對(duì)于每次申請(qǐng)內(nèi)存大小都比較固定的而叼,這個(gè)方式是沒有問題的。

開始和 Heap_1 差不多豹悬, 在內(nèi)存中開辟了一個(gè)靜態(tài)數(shù)組作為堆的空間葵陵,定義大小,字節(jié)對(duì)齊處理等瞻佛。

建立鏈表

Heap_2 通過一個(gè)鏈表維護(hù)未分配的內(nèi)存脱篙,鏈表節(jié)點(diǎn)定義:

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;

兩個(gè)變量分別是指向下一塊內(nèi)存的地址指針 pxNextFreeBlock 以及自己的內(nèi)存大小 xBlockSize。

在第一次申請(qǐng)內(nèi)存的時(shí)候會(huì)調(diào)用初始化函數(shù) prvHeapInit() 初始化列表伤柄。初始化包括鏈表頭 xStart 和鏈表尾 xEnd (這兩個(gè)節(jié)點(diǎn)不包含空閑內(nèi)存)绊困,以及把整個(gè)堆作為一個(gè)完整的空閑節(jié)點(diǎn)。

每塊內(nèi)存(分配出的和未分配的)的結(jié)構(gòu)如下 :

下一個(gè)空閑塊地址 當(dāng)前塊大小 當(dāng)前塊可用內(nèi)存
xx xx xx

每塊的開頭節(jié)點(diǎn)數(shù)據(jù)适刀,提供了分配內(nèi)存或者回收內(nèi)存所需要的信息秤朗。

分配內(nèi)存

當(dāng)我們嘗試申請(qǐng)內(nèi)存的時(shí)候,除了和 Heap_1 一樣進(jìn)行對(duì)齊等處理外笔喉,系統(tǒng)會(huì)在我們申請(qǐng)內(nèi)存大小 xWantedSize 的基礎(chǔ)上增加一個(gè) heapSTRUCT_SIZE (鏈表節(jié)點(diǎn)對(duì)齊后的大腥∈印)的鏈表節(jié)點(diǎn),記錄這塊分配出去的內(nèi)存的大小常挚,供回收的時(shí)候使用作谭。

從鏈表頭開始遍歷未分配內(nèi)存鏈表,查找符合大小的內(nèi)存塊(鏈表按內(nèi)存塊大小排列奄毡,所以最先返回的的塊最符合申請(qǐng)內(nèi)存大小折欠,所謂的最匹配算法就是這個(gè)意思來的)。返回該塊 heapSTRUCT_SIZE 個(gè)字節(jié)后的地址給函數(shù)調(diào)用者, 前面預(yù)留的字節(jié)保留鏈表節(jié)點(diǎn)信息锐秦。

同時(shí)會(huì)判斷當(dāng)前這塊內(nèi)存是否有剩余(大于一個(gè)鏈表節(jié)點(diǎn)所需空間)咪奖,如果有,就把剩余的內(nèi)存再新建一個(gè)未分配內(nèi)存塊節(jié)點(diǎn)农猬,插入到未分配鏈表中赡艰,供下次分配使用售淡。其中 prvInsertBlockIntoFreeList() 這個(gè)宏函數(shù)是把節(jié)點(diǎn)按大小插入到鏈表中斤葱。

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;
    // 尋找匹配的內(nèi)存塊節(jié)點(diǎn)
    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. */
        //返回給我們用地址,前面 heapSTRUCT_SIZE 保存鏈表節(jié)點(diǎn)數(shù)據(jù)
        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. */
        // 內(nèi)存塊比較大揖闸,拆分多余的內(nèi)存揍堕,插回到鏈表
        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. */
            // 按內(nèi)存大小插入
            prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
        }

        xFreeBytesRemaining -= pxBlock->xBlockSize;
    }
}

回收內(nèi)存

回收內(nèi)存, 拿到分配時(shí)返回的地址汤纸,向前索引到對(duì)應(yīng)鏈表節(jié)點(diǎn)衩茸,取出這塊返回內(nèi)存塊的信息,調(diào)用鏈表插入函數(shù)贮泞,將這個(gè)節(jié)點(diǎn)歸還楞慈。(線程安全)

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 );
}
xTaskResumeAll();

!?胁痢囊蓝! 這里感覺,如果隨便傳個(gè)地址進(jìn)來令蛉,會(huì)不會(huì)把系統(tǒng)弄傻.....

Heap_3

Heap_3 實(shí)現(xiàn)是直接對(duì)標(biāo)準(zhǔn)庫(kù)的 malloc 進(jìn)行封裝聚霜, 保證線程安全。

void *pvPortMalloc( size_t xWantedSize ) 
{ 
void *pvReturn; 
    vTaskSuspendAll();  // 掛起任務(wù)
    { 
        pvReturn = malloc( xWantedSize ); 
    } 
    xTaskResumeAll(); 
    return pvReturn; 
} 
void vPortFree( void *pv ) 
{ 
    if( pv != NULL ) 
    { 
        vTaskSuspendAll(); 
        { 
            free( pv ); 
        } 
        xTaskResumeAll(); 
    } 
} 

!! 這種模式下珠叔,堆大小不再由 FreeRTOSConfig.h 中定義的常量 configTOTAL_HEAP_SIZE 決定蝎宇,而是由連接器或者啟動(dòng)代碼決定。

Heap_4

相比 Heap_2, Heap_4 能夠把內(nèi)存碎片合并成大塊內(nèi)存祷安,為了實(shí)現(xiàn)這個(gè)合并算法姥芥,空閑內(nèi)存鏈表是按內(nèi)存地址大小進(jìn)行存儲(chǔ)的(Heap_2 是按照內(nèi)存塊大小進(jìn)行存儲(chǔ))。

xEnd 的位置

不同 heap_2 中 用一個(gè)靜態(tài)變量 xEnd 作為鏈表尾汇鞭,heap_4 把鏈表尾放在了堆的最后位置凉唐,如源碼:

// 堆地址最后往回推一個(gè)鏈表節(jié)點(diǎn)的空間
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
uxAddress -= xHeapStructSize;
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;

xBlockAllocatedBit 判斷

另外,為了安全虱咧,增加一個(gè)位(xBlockSize 的最高位)標(biāo)記檢測(cè) Free 時(shí)傳入地址的正確性熊榛,在初始化的時(shí)候設(shè)置 xBlockAllocatedBit 的值, 一個(gè) size_t 大小的值最高位置1, 分配出去的內(nèi)存塊鏈表節(jié)點(diǎn)的 xBlockSize 或上腕巡,回收的時(shí)候判斷玄坦,如果最高位不是1, 說明出錯(cuò)。

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

進(jìn)行字節(jié)對(duì)齊和線程安全等的操作和前面兩種方式差不多煎楣。

鏈表插入 (合并實(shí)現(xiàn))

Heap_2 中的鏈表插入是通過宏實(shí)現(xiàn)的豺总,按內(nèi)存塊大小進(jìn)行插入,而 Heap_4 的插入操作是一個(gè)函數(shù)择懂,該函數(shù)按內(nèi)存塊地址進(jìn)行插入(低位前)喻喳,這么做是為了實(shí)現(xiàn)內(nèi)存塊合并。
如下困曙, 準(zhǔn)備插入的內(nèi)存塊p表伦, 系統(tǒng)查找到內(nèi)存地址對(duì)應(yīng)與其前面的內(nèi)存塊A, 判斷 A 和 P 之間是否還有其他分配的塊,如果沒有慷丽,直接合并蹦哼; 然后再判斷和內(nèi)存C 的位置關(guān)系,沒有其他分配了的內(nèi)存塊的話要糊,就直接合并纲熏。

內(nèi)存塊 .. 內(nèi)存塊 A 準(zhǔn)備插入內(nèi)存塊 P 內(nèi)存塊 C
xx xx xx xx

下面對(duì)應(yīng)看看源碼 :

static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
    BlockLink_t *pxIterator;
    uint8_t *puc;
    // for 的目的是知道 pxIterator 指向內(nèi)存塊A
    // pxIterator->pxNextFreeBlock 指向內(nèi)存塊C
    // 插入內(nèi)存塊P 剛好夾在中間
    for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
    {
        /* Nothing to do here, just iterate to the right position. */
    }
    // 判斷內(nèi)存塊A 能否與插入內(nèi)存塊P 合并
    // 合并條件:頭尾銜接剛好
    puc = ( uint8_t * ) pxIterator;
    if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
    {
        pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
        pxBlockToInsert = pxIterator;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    // 判斷內(nèi)存塊C 能否與插入塊P 合并
    puc = ( uint8_t * ) pxBlockToInsert;
    if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
    {
        // 如果是內(nèi)存塊C 是最后一個(gè)節(jié)點(diǎn) xEnd,不能合并
        if( pxIterator->pxNextFreeBlock != pxEnd )
        {
            pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
            pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
        }
        else
        {
            pxBlockToInsert->pxNextFreeBlock = pxEnd;
        }
    }
    else
    {
        // 內(nèi)存塊C 不能合并的話锄俄,讓上一塊指向他
        pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
    }
    if( pxIterator != pxBlockToInsert )
    {   
        // 內(nèi)存塊A 不能合并局劲,更新他指向剛插入的內(nèi)存塊P
        pxIterator->pxNextFreeBlock = pxBlockToInsert;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

分配內(nèi)存

相比 Heap_2 差別不大,主要是在分配過程多了一個(gè)位標(biāo)記防止出錯(cuò)奶赠,因?yàn)槭褂昧?xBlockSize 的最高位做標(biāo)記鱼填,所以實(shí)際傳入的 xWantedSize 最高位不能為1,否則超出范圍车柠。
其他差別不大剔氏,此處不做贅述。

回收內(nèi)存

相比 Heap_2, Heap_4 多了一些檢查竹祷,更加安全谈跛。

// 判斷位標(biāo)記,判斷指向下一個(gè)節(jié)點(diǎn)是否 設(shè)置為 Null
configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
configASSERT( pxLink->pxNextFreeBlock == NULL );
if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
{
    if( pxLink->pxNextFreeBlock == NULL )
    {
    ...

最后清除 xBlockSize 的高位標(biāo)記塑陵,調(diào)用插入函數(shù)歸還內(nèi)存感憾。

獲取歷史堆剩余最小值

可用于最壞情況下,堆的使用情況令花, 在每次調(diào)用 pvPortMalloc() 中進(jìn)行更新阻桅。

size_t xPortGetMinimumEverFreeHeapSize( void )

Heap_5

前面方式1、2和4 方式都是靜態(tài)申請(qǐng)一個(gè)數(shù)組作為堆兼都,Heap_5 允許使用多個(gè)不連續(xù)的區(qū)域組成堆嫂沉,申請(qǐng)函數(shù)前,必須通過函數(shù) vPortDefineHeapRegions() 進(jìn)行設(shè)置扮碧,對(duì)于一些內(nèi)存分布不連續(xù)的嵌入式設(shè)備還是很有價(jià)值的趟章,之后其他操作杏糙,和 Heap_4 基本一致。

所以這里主要分析下 vPortDefineHeapRegions() 的實(shí)現(xiàn)蚓土。

設(shè)置作為堆的區(qū)域需要用到一下結(jié)構(gòu)體

/* Used by heap_5.c. */
typedef struct HeapRegion
{
    uint8_t *pucStartAddress;
    size_t xSizeInBytes;
} HeapRegion_t;

舉個(gè)例子宏侍,
有兩個(gè)不連續(xù)區(qū)域首地址0x00200000, 長(zhǎng)度0x10000 和首地址0x00300000蜀漆, 長(zhǎng)度0xA0000 只做為堆谅河,則可以建立如下數(shù)組傳遞給 vPortDefineHeapRegions(), 進(jìn)行設(shè)置。

HeapRegion_t xHeapRegions[] =  
{  
    { ( uint8_t * ) 0x00200000UL, 0x10000 },   
    { ( uint8_t * ) 0x00300000UL, 0xA0000 },   
    { NULL, 0 }         // 告知結(jié)束         
};  

相比前面其他模式确丢,初始化時(shí)绷耍,把可以分配的內(nèi)存作為一個(gè)節(jié)點(diǎn),Heap_5 就把這幾個(gè)不連續(xù)的內(nèi)存區(qū)域分別作為節(jié)點(diǎn)鏈接起來蠕嫁。

每個(gè)連續(xù)區(qū)域作為一個(gè)節(jié)點(diǎn)锨天,其結(jié)構(gòu)如下

指向末端節(jié)點(diǎn)
本塊內(nèi)存區(qū)域大小
本塊可用內(nèi)存區(qū)域
指向下一塊內(nèi)存
0

參考

Using the FreeRTOS Real Time Kernel - A Practical Guide_opened
FreeRTOS memory

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市剃毒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搂赋,老刑警劉巖赘阀,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脑奠,居然都是意外死亡基公,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門宋欺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轰豆,“玉大人,你說我怎么就攤上這事齿诞∷嵝荩” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵祷杈,是天一觀的道長(zhǎng)斑司。 經(jīng)常有香客問我,道長(zhǎng)但汞,這世上最難降的妖魔是什么宿刮? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮私蕾,結(jié)果婚禮上僵缺,老公的妹妹穿的比我還像新娘。我一直安慰自己踩叭,他們只是感情好磕潮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般揉抵。 火紅的嫁衣襯著肌膚如雪亡容。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天冤今,我揣著相機(jī)與錄音闺兢,去河邊找鬼。 笑死戏罢,一個(gè)胖子當(dāng)著我的面吹牛屋谭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播龟糕,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼桐磁,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了讲岁?” 一聲冷哼從身側(cè)響起我擂,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缓艳,沒想到半個(gè)月后校摩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阶淘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年衙吩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溪窒。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坤塞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出澈蚌,到底是詐尸還是另有隱情摹芙,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布惜浅,位于F島的核電站瘫辩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏坛悉。R本人自食惡果不足惜伐厌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望裸影。 院中可真熱鬧挣轨,春花似錦、人聲如沸轩猩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至晤锹,卻和暖如春摩幔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鞭铆。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工或衡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人车遂。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓封断,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親舶担。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坡疼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容