【freertos】008-記憶體管理

李柱明發表於2022-05-22


前言

本章主要講解內部儲存空間(RAM)的管理。

詳細分析heap5方案。

參考:

8.1 C標準庫的記憶體管理

C標準庫的記憶體管理用到的API是malloc()free(),但是不建議在RTOS中直接呼叫,因為:

  1. C標準庫的記憶體管理實現可能比較大,不適合小型嵌入式RAM不足的裝置。
  2. 可能會產生記憶體碎片,對於安全性要求高的嵌入式裝置不適合。
  3. 這兩個函式會使得連結器配置得複雜。
  4. 待補充。

8.2 freertos記憶體管理介面

freertos的記憶體管理和核心實現是相互獨立的,核心規定記憶體管理介面,而介面內容卻是可由外部自由實現。

但是freertos官方也提供了幾種記憶體分配演算法:heap1、heap2、heap3、heap4、heap5。

所以,需要記憶體管理的有合適的演算法可以單獨使用freertos提供記憶體分配演算法到自己的裝置或系統中。

記憶體堆大小由巨集configTOTAL_HEAP_SIZE決定。(heap3方案除外)

/*
 * Map to the memory management routines required for the port.
 */
void * pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION;    //記憶體申請函式
void vPortFree( void * pv ) PRIVILEGED_FUNCTION;            //記憶體釋放函式
void vPortInitialiseBlocks( void ) PRIVILEGED_FUNCTION;     //初始化記憶體堆函式
size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION;    //獲取當前未分配的記憶體堆大小
size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION; //獲取未分配的記憶體堆歷史最小值

8.3 freertos五種記憶體管理

簡單介紹。

8.3.1 heap1

特點:

  1. 只能申請不能釋放。
  2. 不會產生記憶體碎片。
  3. 函式的執行時間是確定的。因為可直接查到空閒空間地址和大小。

應用:這種方案一般用在安全性要求較高的系統中。用於從不刪除任務、佇列、訊號量、互斥量等的應用程式。

實現:

使用xNextFreeByte來定位下一個空閒的記憶體堆位置。

因為freertos系統堆是一個大陣列,所以,記憶體空間是連續的。

所以xNextFreeByte值在heap1方案中實際儲存的是已經被分配的記憶體大小,下次申請時跳過這些已申請的,剩下就是空閒空間。

pucAlignedHeap是一個指向對齊後的記憶體堆起始地址。

使用者提供的系統堆記憶體的起始地址不一定是對齊的記憶體地址,需要糾正,糾正後的系統堆記憶體起始地址儲存在pucAlignedHeap

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

API注意:

  1. vPortInitialiseBlocks()僅僅將靜態區域性變數xNextFreeByte設定為0,表示記憶體沒有被申請。
  2. xPortGetFreeHeapSize()並不是釋放記憶體,因為heap1方案不支援釋放,所以該API是獲取當前未分配的記憶體堆大小。

8.3.2 heap2

特點:

  1. 支援動態申請和釋放。
  2. 連結串列管理,但是不支援拼接相鄰空閒塊。所以容易產生記憶體碎片。
  3. 申請時間具有不確定性,因為檢索空閒塊需要檢索連結串列,空閒塊多、記憶體碎片多時,檢索會久點。但是效率比標準C庫中的malloc函式高得多。

應用:不建議用於記憶體分配和釋放是隨機大小的應用程式。

實現:

heap2方案的記憶體管理連結串列:

typedef struct A_BLOCK_LINK {
    struct A_BLOCK_LINK *pxNextFreeBlock;
    size_t xBlockSize;
} BlockLink_t;

pxNextFreeBlock:是指向下一個空閒記憶體塊的指標。

xBlockSize:記錄當前記憶體塊大小。(記憶體管理函連結串列結構體)

8.3.3 heap3

特點:

  1. 需要連結器設定一個堆,malloc()和free()函式由編譯器提供。
  2. 具有不確定性。
  3. 很可能增大RTOS核心的程式碼大小。
  4. configTOTAL_HEAP_SIZE不起作用,因為堆空間由編譯器決定提供的。一般在啟動檔案裡設定。

實現:

heap3方案只是簡單的封裝了標準C庫中的malloc()和free()函式。

重新封裝後的malloc()和free()函式具有保護功能,採用的封裝方式是操作記憶體前掛起排程器、完成後再恢復排程器。

8.3.4 heap4

和heap2方案類似,且支援相鄰空閒塊拼接,降低記憶體塊碎片化機率。

特點:

  1. 支援動態申請和釋放。
  2. 按地址升序,優先返回第一個滿足size需求的空閒塊。
  3. 連結串列管理,支援相鄰空閒塊拼接。
  4. 申請時間具有不確定性,因為檢索空閒塊需要檢索連結串列,但是效率比標準C庫中的malloc函式高得多。

應用:

  • 可用於重複刪除任務、佇列、訊號量、互斥量等的應用程式。
  • 可用於分配和釋放隨機位元組記憶體的應用程式。

8.3.5 heap5

heap_5.c方案在實現動態記憶體分配時與heap4.c方案一樣,採用最佳匹配演算法和合並演算法。

並且允許記憶體堆跨越多個非連續的記憶體區。

如可以在片內RAM中定義一個記憶體堆,還可以在外部SDRAM再定義一個或多個記憶體堆,這些記憶體都歸系統管理。

heap1、heap2、heap4的堆空間都是有個大陣列,擴充下,支援非連續的記憶體堆,可以使用多個大陣列啊。

特點:

  1. 支援動態申請和釋放。
  2. 按地址升序,優先返回第一個滿足size需求的空閒塊。
  3. 連結串列管理,支援相鄰空閒塊拼接。
  4. 支援記憶體堆跨越多個非連續的記憶體區。
  5. 申請時間具有不確定性,因為檢索空閒塊需要檢索連結串列,但是效率比標準C庫中的malloc函式高得多。

實現:

各塊記憶體堆管理結構體:

typedef struct HeapRegion
{
    uint8_t *pucStartAddress; // 記憶體堆起始地址
    size_t xSizeInBytes;      // 記憶體塊大小
} HeapRegion_t;

初始化後的記憶體如下:

8.4 heap5記憶體管理實現細節

實現的記憶體管理特點如下:

  1. 支援動態申請和動態釋放。
  2. 按地址升序,優先返回第一個滿足size需求的空閒塊。
  3. 支援相鄰空閒塊拼接。
  4. 支援多個非連續堆空間。
  5. 注意:在建立核心元件前,先呼叫vPortDefineHeapRegions()完成系統堆初始化。

8.4.1 相關介面

/* Map to the memory management routines required for the port. */

/* 先初始化多個非連續堆空間 */
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions ) PRIVILEGED_FUNCTION;
/* 記憶體申請函式 */
void * pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION;
/* 記憶體釋放函式 */
void vPortFree( void * pv ) PRIVILEGED_FUNCTION;


void vPortInitialiseBlocks( void ) PRIVILEGED_FUNCTION;     // 初始化記憶體堆函式
size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION;    // 獲取當前未分配的記憶體堆大小
size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION; // 獲取未分配的記憶體堆歷史最小值
void vPortGetHeapStats( HeapStats_t *xHeapStats ); // 提供堆狀態資訊

8.4.2 相關引數

xFreeBytesRemaining:表示當前系統中未分配的記憶體堆大小。

xMinimumEverFreeBytesRemaining:表示未分配記憶體堆空間歷史最小的記憶體值。為了瞭解最壞情況下記憶體堆的使用情況。

xBlockAllocatedBit:系統最高位位元位1。如在32位系統下,該變數值位0x80000000。

  • 通常用法:噹噹前記憶體塊被佔用時,當前記憶體塊size記錄值會與該變數按位或,標誌當前記憶體塊被佔用。

xStart:記憶體管理塊連結串列頭節點,用於指向第一個記憶體塊。

pxEnd:記憶體管理塊連結串列尾節點指標,用於表示後面沒有合法記憶體空間。

xNumberOfSuccessfulAllocations:申請成功的次數。

xHeapStructSize:某個記憶體塊的管理結構的size,該size也是(向上)位元組對齊的。

heapMINIMUM_BLOCK_SIZE:記憶體塊size下限。(記憶體塊管理區+資料區),在heap5方案中是兩倍的xHeapStructSize,即是資料區最小也要有一個xHeapStructSize大小。

8.4.3 資料結構

8.4.3.1 各非連續堆塊管理資料結構

各大塊堆空間管理資料結構型別HeapRegion_t

  • pucStartAddress:該塊堆空間記憶體起始地址。
  • xSizeInBytes:該塊堆空間大小。
typedef struct HeapRegion
{
    uint8_t * pucStartAddress;
    size_t xSizeInBytes;
} HeapRegion_t;

例如,如果定義兩個非連續堆空間:

  • 第一個記憶體塊大小為0x10000位元組,起始地址為0x80000000;
  • 第二個記憶體塊大小為0xa0000位元組,起始地址為0x90000000。
  • 按照地址順序放入到陣列中,地址小的在前。
  • 該資料結構供給堆空間初始化API vPortDefineHeapRegions()使用。
const HeapRegion_t xHeapRegions[] = {
        { ( uint8_t * ) 0x80000000UL, 0x10000 },
        { ( uint8_t * ) 0x90000000UL, 0xa0000 },
        { NULL, 0 } /* 陣列結尾 */
    };

8.4.3.2 記憶體塊管理資料結構

記憶體塊管理資料結構型別BlockLink_t

  • pxNextFreeBlock:是指向下一個空閒記憶體塊的指標。

  • xBlockSize:記錄當前記憶體塊大小。(記憶體管理函連結串列結構體)

    • 需要特別注意的是,這個記憶體塊大小不僅僅代表當前記憶體塊可用空間,還表示當前記憶體塊是否空閒:該變數最高位為0時,表示空閒,為1時,表示被佔用。(參考:heapBITS_PER_BYTE
  • 就是單向連結串列。

typedef struct A_BLOCK_LINK
{
    struct A_BLOCK_LINK * pxNextFreeBlock; /* 下一個空閒記憶體塊的指標 */
    size_t xBlockSize;                     /* 當前記憶體塊大小 */
} BlockLink_t

8.4.3.3 主要資料

除了資料結構型別外,管理資料結構還需要一些變數來實現。

連結串列頭xStart

  • 記憶體塊連結串列頭。
static BlockLink_t xStart;

連結串列尾指標pxEnd

  • 記憶體塊連結串列尾。
static BlockLink_t *pxEnd = NULL;

8.4.4 初始化堆空間:vPortDefineHeapRegions()

函式原型:

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
  • pxHeapRegions:傳入儲存非連續堆空間地址的資料結構地址。

    • HeapRegion_t:各非連續塊堆空間管理資料結構型別。

8.4.4.1 資料校驗

初始時記憶體塊連結串列尾指標為空,表示沒有被初始化過,沒有被使用過才能重置這些堆塊。

configASSERT( pxEnd == NULL );

如果有堆塊被初始化過了,記憶體塊連結串列尾指標不應該為空。

同時,新的堆塊地址在舊塊後。

/* 初始化非首個堆塊 */
if(xDefinedRegions != 0)
{
    /* 如果初始化過堆塊,肯定有尾節點的 */
    configASSERT( pxEnd != NULL );

    /* 新的堆塊起始地址要在前面堆塊尾地址後 */
    configASSERT( xAddress > ( size_t ) pxEnd );
}

8.4.4.2 地址對齊

使用者傳入的堆塊空間始末地址不一定符合地址對齊的,在初始化時需要裁剪,使堆塊地址向內對齊。

首地址向上對齊:

if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) /* 如果還沒對齊,需要實現向上對齊 */
{
    /* 先向上漂移到下一個對齊空間 */
    xAddress += ( portBYTE_ALIGNMENT - 1 );
    /* 去除餘數,即是往當前對齊空間下對齊 */
    xAddress &= ~portBYTE_ALIGNMENT_MASK;

    /* 更新對齊後,可用的實際空間。即是減去對齊丟棄的空間 */
    xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress;
}

/* 對齊後的堆塊起始地址 */
xAlignedHeap = xAddress;

同時,在堆塊尾部需要預留出尾部連結串列節點的空間,該空間起始地址也需要符合地址對齊。

8.4.4.3 頭節點:xStart

記憶體管理塊連結串列的頭節點未xStart,該節點只是一個哨兵,不含資料,只指向第一個堆塊。

該節點的實際空間並不在堆塊內,而記憶體管理塊節點和xpEnd指向的尾節點的空間都是存在堆塊內部。

8.4.4.4 記憶體管理塊

記憶體管理塊資料結構內容參考前面。

剛剛初始化的堆塊記憶體,一整塊都是空閒空間,但是這些空間需要記憶體管理塊資料結構來管理。

heap5方案與其它方案不一樣,需要支援不連續地址,,其實現是在堆塊尾部需要預留一個記憶體管理塊節點空間出來,用於對接下一個堆塊。

所以初始化完堆塊後,其內部結構如下圖所示:

8.4.4.5 完整程式碼實現

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions )
{
    BlockLink_t * pxFirstFreeBlockInRegion = NULL, * pxPreviousFreeBlock;
    size_t xAlignedHeap;
    size_t xTotalRegionSize, xTotalHeapSize = 0;
    BaseType_t xDefinedRegions = 0;
    size_t xAddress;
    const HeapRegion_t * pxHeapRegion;

    /* 沒有被初始化過才能往下執行 */
    configASSERT( pxEnd == NULL );

    /* 獲取第一個堆塊的地址 */
    pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );

    /* 逐塊初始化 */
    while( pxHeapRegion->xSizeInBytes > 0 )
    {
        /* 記錄當前堆塊空間大小 */
        xTotalRegionSize = pxHeapRegion->xSizeInBytes;

        /* 確保各堆塊起始地址符合對齊要求 */
        xAddress = ( size_t ) pxHeapRegion->pucStartAddress;

        if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) /* 如果還沒對齊,需要實現向上對齊 */
        {
            /* 先向上漂移到下一個對齊空間 */
            xAddress += ( portBYTE_ALIGNMENT - 1 );
            /* 去除餘數,即是往當前對齊空間下對齊 */
            xAddress &= ~portBYTE_ALIGNMENT_MASK;

            /* 更新對齊後,可用的實際空間。即是減去對齊丟棄的空間 */
            xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress;
        }

        /* 對齊後的堆塊起始地址 */
        xAlignedHeap = xAddress;

        if( xDefinedRegions == 0 ) /* 初始化首個堆塊 */
        {
            /* xStart為哨兵節點,不帶資料,只帶指向首個堆塊 */
            xStart.pxNextFreeBlock = ( BlockLink_t * ) xAlignedHeap;
            xStart.xBlockSize = ( size_t ) 0;
        }
        else /* 初始化非首個堆塊 */
        {
            /* 如果初始化過堆塊,肯定有尾節點的 */
            configASSERT( pxEnd != NULL );

            /* 新的堆塊起始地址要在前面堆塊尾地址後 */
            configASSERT( xAddress > ( size_t ) pxEnd );
        }

        /* 備份系統堆塊尾部節點 */
        pxPreviousFreeBlock = pxEnd;

        /* 在新堆塊末留出尾節點空間,用於表示系統堆塊空間結束點 */
        xAddress = xAlignedHeap + xTotalRegionSize;
        xAddress -= xHeapStructSize;
        xAddress &= ~portBYTE_ALIGNMENT_MASK;
        pxEnd = ( BlockLink_t * ) xAddress;
        pxEnd->xBlockSize = 0;
        pxEnd->pxNextFreeBlock = NULL;

        /* 在當前堆塊內前部空間作為當前堆塊管理資料結構 */
        pxFirstFreeBlockInRegion = ( BlockLink_t * ) xAlignedHeap;
        /* 當前堆塊當前可給使用者使用的空間大小。(對比原有,少了首位對齊位元組、少了首位記憶體塊管理資料結構空間) */
        pxFirstFreeBlockInRegion->xBlockSize = xAddress - ( size_t ) pxFirstFreeBlockInRegion;
        /* 當前堆塊的下一個堆塊指向尾節點,表示當前為最後一個堆塊 */
        pxFirstFreeBlockInRegion->pxNextFreeBlock = pxEnd;

        /* 如果當前不是首個初始化的堆塊,就需要拼接到前面初始化的堆塊中 */
        if( pxPreviousFreeBlock != NULL )
        {
            /* 前一塊堆塊尾部節點的下一個堆塊指向當前堆塊 */
            pxPreviousFreeBlock->pxNextFreeBlock = pxFirstFreeBlockInRegion;
        }

        /* 記錄總堆塊可用空間 */
        xTotalHeapSize += pxFirstFreeBlockInRegion->xBlockSize;

        /* 移到下一個需要初始化的堆塊 */
        xDefinedRegions++;
        pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );
    }

    /* 初始化未分配記憶體堆歷史最小值。用於瞭解最壞情況下,記憶體堆的使用情況。 */
    xMinimumEverFreeBytesRemaining = xTotalHeapSize;
    /* 當前系統未分配記憶體堆大小 */
    xFreeBytesRemaining = xTotalHeapSize;

    /* 系統堆必須有空間可用才能在後面被訪問 */
    configASSERT( xTotalHeapSize );

    /* 系統最高位標記為1。
        如32為系統時,該值為0x80000000。
        用於標記記憶體塊是否空閒。被佔用時,將記憶體塊節點內的記憶體塊大小xBlockSize和該值按位或。 */
    xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

8.4.5 記憶體塊插入空閒連結串列:prvInsertBlockIntoFreeList()

這是一個內部介面函式,使用者不會接觸到,但是後面的malloc和free的實現會使用到,所以在這裡先分析了。

原型

static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert );

8.4.5.1 檢索記憶體塊相鄰的方法

插入空閒塊是按空閒塊地址順序插入的。

需要找到當前空閒塊起始地址比新插入的空閒塊起始地址小,且當前空閒塊連結串列指向的下一個空閒塊的起始地址比新插入的空閒塊起始地址大,新的空閒塊就是需要插入到這兩中間。

/* 先找到和pxBlockToInsert相鄰的前一個空閒塊 */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
    /* Nothing to do here, just iterate to the right position. */
}

8.4.5.2 合併記憶體塊

檢查記憶體塊地址連續的方法就是:本塊記憶體起始地址+本塊記憶體大小==下一塊記憶體起始地址

/* 如果前一個空閒塊和pxBlockToInsert在地址上是連續的,就和前一塊合併 */
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
    /* ... */
}

/* 如果pxBlockToInsert和其下一個空閒塊地址連續,就和下一個空閒塊合併 */
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
    /* ... */  
}

8.4.5.3 完整程式碼實現

static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert )
{
    BlockLink_t * pxIterator;
    uint8_t * puc;

    /* 先找到和pxBlockToInsert相鄰的前一個空閒塊 */
    for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
    {
        /* Nothing to do here, just iterate to the right position. */
    }

    /* 獲取pxBlockToInsert相鄰的前一個空閒塊的起始地址 */
    puc = ( uint8_t * ) pxIterator;

    /* 如果前一個空閒塊和pxBlockToInsert在地址上是連續的,就和前一塊合併 */
    if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
    {
        /* size先合併 */
        pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
        /* 記憶體管理塊也統一下 */
        pxBlockToInsert = pxIterator;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    /* 獲取當前需要插入的空閒塊。(可能是合併後的,也可能是沒有合併的,都是下一個空閒塊的前一個空閒塊) */
    puc = ( uint8_t * ) pxBlockToInsert;

    /* 如果pxBlockToInsert和其下一個空閒塊地址連續,就和下一個空閒塊合併 */
    if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
    {   
        if( pxIterator->pxNextFreeBlock != pxEnd ) /* 如果pxBlockToInsert的下一個空閒塊的下一個空閒節點不是尾節點,便需要合併 */
        {
            /* 合併size */
            pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
            /* 更新指向。合併時,pxBlockToInsert指向原有下一個空閒塊的下一個空閒塊 */
            pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
        }
        else /* 如果pxBlockToInsert的下一個空閒塊的空閒節點是尾節點 */
        {
            /* 合併時,不需要擴充size,只需要更新指向即可 */
            pxBlockToInsert->pxNextFreeBlock = pxEnd;
        }
    }
    else
    {
        /* 如果不能合併,就直接插入 */
        pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
    }

    /* 如果新插入的空閒塊沒有和前一個空閒塊合併,插入新的空閒塊後需要更新連結串列節點指向 */
    if( pxIterator != pxBlockToInsert )
    {
        pxIterator->pxNextFreeBlock = pxBlockToInsert;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

8.4.6 記憶體申請:pvPortMalloc()

8.4.6.1 原型

void * pvPortMalloc( size_t xWantedSize )

xWantedSize:輸入使用者需要申請的空間大小。

返回:

  • 申請成功後返回使用者可用空間的起始地址。
  • 申請失敗返回NULL。

8.4.6.2 引數校驗

pxEnd不為空證明該介面對應的堆空間已經被初始化過。

/* 堆塊必須被初始化過 */
configASSERT( pxEnd );

xWantedSize該值不能大到頂部標誌都被覆蓋掉。

/* 需要申請的記憶體不能大到頂部標誌都被覆蓋掉。 */
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
    /* ... */
}

8.4.6.3 引數糾正

xWantedSize變數先是使用者傳入的期望申請的大小,而內部空閒塊管理除了給使用者使用的資料區外還需要記憶體塊管理區,所以需要新增一個xHeapStructSize

/* 一個記憶體塊除了資料區外,還需要管理區BlockLink_t */
xWantedSize += xHeapStructSize;

還需要位元組向上對齊:

  • 向上對齊是size往更大方向對齊。
  • 防止溢位是防止xWantedSize擴大後不能大到覆蓋掉頂部標誌位。
/* 確保申請的記憶體大小也符合位元組對齊 */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
    /* 要往上對齊,且要防止溢位 */
    if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize )
    {
        xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
    }
    else
    {
        xWantedSize = 0;
    }
}

8.4.6.4 拆分記憶體塊

如果檢索到size符合要求的第一個空閒塊,malloc會檢查該塊除了給使用者使用的空間和記憶體管理塊的空間後,剩餘的空間是否滿足組建新的空閒塊,如果滿足就進行拆分。

/* 如果當前空閒塊滿足使用者需求後,還剩足夠的空間元件另一個空閒塊,那就需要拆分 */
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
    /* 組建新的空閒塊,其起始地址為上一個空閒塊地址+xWantedSize偏移就是了 */
    pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );

    /* 新的空閒塊節點size處理。 */
    pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
    pxBlock->xBlockSize = xWantedSize;

    /* 新的空閒塊插入到空閒連結串列中 */
    prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
}

8.4.6.5 完整程式碼實現

void * pvPortMalloc( size_t xWantedSize )
{
    BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink;
    void * pvReturn = NULL;

    /* 堆塊必須被初始化過 */
    configASSERT( pxEnd );

    vTaskSuspendAll();
    {
        /* 需要申請的記憶體不能大到頂部標誌都被覆蓋掉。 */
        if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
        {
            /* 申請的size大於0,且加上記憶體管理塊後不能溢位 */
            if( ( xWantedSize > 0 ) && ( ( xWantedSize + xHeapStructSize ) >  xWantedSize ) ) 
            {
                /* 一個記憶體塊除了資料區外,還需要管理區BlockLink_t */
                xWantedSize += xHeapStructSize;

                /* 確保申請的記憶體大小也符合位元組對齊 */
                if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
                {
                    /* 要往上對齊,且要防止溢位 */
                    if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize )
                    {
                        xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
                    }
                    else
                    {
                        xWantedSize = 0;
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                xWantedSize = 0;
            }

            /* 申請的記憶體佔用的size要小於現有空閒size才會進行檢索 */
            if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
            {
                /* 從低地址的空閒塊開始檢索 */
                pxPreviousBlock = &xStart;
                pxBlock = xStart.pxNextFreeBlock;

                /* 找出能滿足xWantedSize大小的空閒塊 */
                while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
                {
                    pxPreviousBlock = pxBlock;
                    pxBlock = pxBlock->pxNextFreeBlock;
                }

                /* 確保當前塊不是尾節點 */
                if( pxBlock != pxEnd )
                {
                    /* 返回資料區起始地址給使用者 */
                    pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );

                    /* 把當前塊從空閒連結串列中移除 */
                    pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

                    /* 如果當前空閒塊滿足使用者需求後,還剩足夠的空間元件另一個空閒塊,那就需要拆分 */
                    if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
                    {
                        /* 組建新的空閒塊,其起始地址為上一個空閒塊地址+xWantedSize偏移就是了 */
                        pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );

                        /* 新的空閒塊節點size處理。 */
                        pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
                        pxBlock->xBlockSize = xWantedSize;

                        /* 新的空閒塊插入到空閒連結串列中 */
                        prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* 更新系統堆剩餘空閒空間 */
                    xFreeBytesRemaining -= pxBlock->xBlockSize;

                    /* 更新申請記憶體的歷史最低值 */
                    if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
                    {
                        xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }

                    /* 標記當前塊已被使用 */
                    pxBlock->xBlockSize |= xBlockAllocatedBit;
                    /* 重置節點指向 */
                    pxBlock->pxNextFreeBlock = NULL;
                    /* 更新全域性malloc次數 */
                    xNumberOfSuccessfulAllocations++;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();

    #if ( configUSE_MALLOC_FAILED_HOOK == 1 )
        {
            if( pvReturn == NULL )
            {
                /* 有鉤子就呼叫下鉤子,一般用於除錯或記錄 */
                extern void vApplicationMallocFailedHook( void );
                vApplicationMallocFailedHook();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    #endif /* if ( configUSE_MALLOC_FAILED_HOOK == 1 ) */

    return pvReturn;
}

8.4.7 記憶體釋放

8.4.7.1 原型

void vPortFree( void * pv );

pv:需要釋放的記憶體空間的起始地址。

8.4.7.2 簡要分析

通過傳入的地址,偏移後找到該記憶體塊的資料管理結構。

檢查傳入的地址是否合法,記憶體塊是否符合特徵。

在heap5程式碼中檢查記憶體塊是否合法的做法還可以新增一個條件,傳入的地址在堆塊範圍內

檢查合法後,把當前記憶體塊回收到空閒連結串列:

  • 清除被佔用位。
  • 把當前空閒塊插回空閒連結串列。

8.4.7.3 完整程式碼實現

void vPortFree( void * pv )
{
    uint8_t * puc = ( uint8_t * ) pv;
    BlockLink_t * pxLink;

    if( pv != NULL )
    {
        /* 找到該記憶體塊的資料管理結構 */
        puc -= xHeapStructSize;

        /* 型別轉換 */
        pxLink = ( void * ) puc;

        /* 記憶體塊資料管理結構校驗 */
        configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
        configASSERT( pxLink->pxNextFreeBlock == NULL );

        /* 如果當前記憶體塊被分配了,就需要釋放 */
        if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
        {
            if( pxLink->pxNextFreeBlock == NULL )
            {
                /* 先消除被佔用標誌位,這樣該變數才是表示當前記憶體塊的size */
                pxLink->xBlockSize &= ~xBlockAllocatedBit;

                vTaskSuspendAll();
                {
                    /* 把當前塊回收到核心,更新空閒空間size */
                    xFreeBytesRemaining += pxLink->xBlockSize;
                    traceFREE( pv, pxLink->xBlockSize );
                    /* 把當前塊插回空閒連結串列 */
                    prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
                    /* 更新記錄呼叫free次數 */
                    xNumberOfSuccessfulFrees++;
                }
                ( void ) xTaskResumeAll();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
}

8.4.8 獲取總堆空閒size:xPortGetFreeHeapSize()

size_t xPortGetFreeHeapSize( void )
{
    return xFreeBytesRemaining;
}

8.4.9 獲取歷史申請最小空間值:xPortGetMinimumEverFreeHeapSize()

size_t xPortGetMinimumEverFreeHeapSize( void )
{
    return xMinimumEverFreeBytesRemaining;
}

8.4.10 獲取堆狀態資訊

狀態資訊結構體:

/* 用於從vPortGetHeapStats()傳遞關於堆的資訊. */
typedef struct xHeapStats
{
    size_t xAvailableHeapSpaceInBytes;      /* The total heap size currently available - this is the sum of all the free blocks, not the largest block that can be allocated. */
    size_t xSizeOfLargestFreeBlockInBytes;  /* The maximum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
    size_t xSizeOfSmallestFreeBlockInBytes; /* The minimum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
    size_t xNumberOfFreeBlocks;             /* The number of free memory blocks within the heap at the time vPortGetHeapStats() is called. */
    size_t xMinimumEverFreeBytesRemaining;  /* The minimum amount of total free memory (sum of all free blocks) there has been in the heap since the system booted. */
    size_t xNumberOfSuccessfulAllocations;  /* The number of calls to pvPortMalloc() that have returned a valid memory block. */
    size_t xNumberOfSuccessfulFrees;        /* The number of calls to vPortFree() that has successfully freed a block of memory. */
} HeapStats_t;

介面實現:

void vPortGetHeapStats( HeapStats_t * pxHeapStats )
{
    BlockLink_t * pxBlock;
    size_t xBlocks = 0, xMaxSize = 0, xMinSize = portMAX_DELAY; /* portMAX_DELAY used as a portable way of getting the maximum value. */

    vTaskSuspendAll();
    {
        pxBlock = xStart.pxNextFreeBlock;

        /* 堆塊被初始化過才能進去 */
        if( pxBlock != NULL )
        {
            /* 遍歷每一個空閒記憶體塊 */
            do
            {
                /* 記錄當前有多少個空閒記憶體塊(也包括非連續堆塊末節點的記憶體塊,雖然資料區size為0,但是也記錄在內) */
                xBlocks++;

                /* 更新最大塊的size */
                if( pxBlock->xBlockSize > xMaxSize )
                {
                    xMaxSize = pxBlock->xBlockSize;
                }

                /* 需要注意的是heap5支援多個非連續的堆塊,所以非連續堆塊末節點值用於指向下一個記憶體塊,其內資料區size為0 */
                if( pxBlock->xBlockSize != 0 )
                {
                    /* 更新最小塊size */
                    if( pxBlock->xBlockSize < xMinSize )
                    {
                        xMinSize = pxBlock->xBlockSize;
                    }
                }

                /* 遍歷下一個記憶體塊 */
                pxBlock = pxBlock->pxNextFreeBlock;
            } while( pxBlock != pxEnd );
        }
    }
    ( void ) xTaskResumeAll();

    /* 收集部分堆狀態資訊 */
    pxHeapStats->xSizeOfLargestFreeBlockInBytes = xMaxSize;
    pxHeapStats->xSizeOfSmallestFreeBlockInBytes = xMinSize;
    pxHeapStats->xNumberOfFreeBlocks = xBlocks;

    taskENTER_CRITICAL(); /* 進入臨界。維護堆塊管理的全域性變數的原子性 */
    {
        /* 收集剩餘堆狀態資訊 */
        pxHeapStats->xAvailableHeapSpaceInBytes = xFreeBytesRemaining;
        pxHeapStats->xNumberOfSuccessfulAllocations = xNumberOfSuccessfulAllocations;
        pxHeapStats->xNumberOfSuccessfulFrees = xNumberOfSuccessfulFrees;
        pxHeapStats->xMinimumEverFreeBytesRemaining = xMinimumEverFreeBytesRemaining;
    }
    taskEXIT_CRITICAL(); /* 退出臨界 */
}

相關文章