從五大結構體,帶你掌握鴻蒙輕核心動態記憶體Dynamic Memory

華為雲開發者社群發表於2021-06-24
摘要:本文帶領大家一起剖析了鴻蒙輕核心的動態記憶體模組的原始碼,包含動態記憶體的結構體、動態記憶體池初始化、動態記憶體申請、釋放等。

本文分享自華為雲社群《鴻蒙輕核心M核原始碼分析系列九 動態記憶體Dynamic Memory 第一部分》,原文作者:zhushy。

記憶體管理模組管理系統的記憶體資源,它是作業系統的核心模組之一,主要包括記憶體的初始化、分配以及釋放。

在系統執行過程中,記憶體管理模組通過對記憶體的申請/釋放來管理使用者和OS對記憶體的使用,使記憶體的利用率和使用效率達到最優,同時最大限度地解決系統的記憶體碎片問題。

鴻蒙輕核心的記憶體管理分為靜態記憶體管理和動態記憶體管理,提供記憶體初始化、分配、釋放等功能。

  • 動態記憶體:在動態記憶體池中分配使用者指定大小的記憶體塊。
    • 優點:按需分配。
    • 缺點:記憶體池中可能出現碎片。
  • 靜態記憶體:在靜態記憶體池中分配使用者初始化時預設(固定)大小的記憶體塊。
    • 優點:分配和釋放效率高,靜態記憶體池中無碎片。
    • 缺點:只能申請到初始化預設大小的記憶體塊,不能按需申請。

上一系列分析了靜態記憶體,我們開始分析動態記憶體。動態記憶體管理主要用於使用者需要使用大小不等的記憶體塊的場景。當使用者需要使用記憶體時,可以通過作業系統的動態記憶體申請函式索取指定大小的記憶體塊,一旦使用完畢,通過動態記憶體釋放函式歸還所佔用記憶體,使之可以重複使用。

OpenHarmony LiteOS-M動態記憶體在TLSF演算法的基礎上,對區間的劃分進行了優化,獲得更優的效能,降低了碎片率。動態記憶體核心演算法框圖如下:

從五大結構體,帶你掌握鴻蒙輕核心動態記憶體Dynamic Memory

根據空閒記憶體塊的大小,使用多個空閒連結串列來管理。根據記憶體空閒塊大小分為兩個部分:[4, 127]和[27, 231],如上圖size class所示:

  • 對[4,127]區間的記憶體進行等分,如上圖綠色部分所示,分為31個小區間,每個小區間對應記憶體塊大小為4位元組的倍數。每個小區間對應一個空閒記憶體連結串列和用於標記對應空閒記憶體連結串列是否為空的一個位元位,值為1時,空閒連結串列非空。[4,127]區間的記憶體使用1個32位無符號整數點陣圖標記。
  • 大於127位元組的空閒記憶體塊,按照2的次冪區間大小進行空閒連結串列管理。總共分為24個小區間,每個小區間又等分為8個二級小區間,見上圖藍色的Size Class和Size SubClass部分。每個二級小區間對應一個空閒連結串列和用於標記對應空閒記憶體連結串列是否為空的一個位元位。總共24*8=192個二級小區間,對應192個空閒連結串列和192/32=6個32位無符號整數點陣圖標記。

例如,當有40位元組的空閒記憶體需要插入空閒連結串列時,對應小區間[40,43],第10個空閒連結串列,點陣圖標記的第10位元位。把40位元組的空閒記憶體掛載第10個空閒連結串列上,並判斷是否需要更新點陣圖標記。當需要申請40位元組的記憶體時,根據點陣圖標記獲取存在滿足申請大小的記憶體塊的空閒連結串列,從空閒連結串列上獲取空閒記憶體節點。如果分配的節點大於需要申請的記憶體大小,進行分割節點操作,剩餘的節點重新掛載到相應的空閒連結串列上。當有580位元組的空閒記憶體需要插入空閒連結串列時,對應二級小區間[2^9,2^9+2^6],第31+2*8=47個空閒連結串列,第2個點陣圖標記的第17位元位。把580位元組的空閒記憶體掛載第47個空閒連結串列上,並判斷是否需要更新點陣圖標記。當需要申請580位元組的記憶體時,根據點陣圖標記獲取存在滿足申請大小的記憶體塊的空閒連結串列,從空閒連結串列上獲取空閒記憶體節點。如果分配的節點大於需要申請的記憶體大小,進行分割節點操作,剩餘的節點重新掛載到相應的空閒連結串列上。如果對應的空閒連結串列為空,則向更大的記憶體區間去查詢是否有滿足條件的空閒連結串列,實際計算時,會一次性查詢到滿足申請大小的空閒連結串列。

動態記憶體管理結構如下圖所示:

從五大結構體,帶你掌握鴻蒙輕核心動態記憶體Dynamic Memory

  • 記憶體池池頭部分

記憶體池池頭部分包含記憶體池資訊和點陣圖標記陣列和空閒連結串列陣列。記憶體池資訊包含記憶體池起始地址及堆區域總大小,記憶體池屬性。點陣圖標記陣列有7個32位無符號整陣列成,每個位元位標記對應的空閒連結串列是否掛載空閒記憶體塊節點。空閒記憶體連結串列包含223個空閒記憶體頭節點資訊,每個空閒記憶體頭節點資訊維護記憶體節點頭和空閒連結串列中的前驅、後繼空閒記憶體節點。

  • 記憶體池節點部分

包含3種型別節點,未使用空閒記憶體節點,已使用記憶體節點,尾節點。每個記憶體節點維護一個前序指標,指向記憶體池中上一個記憶體節點,維護大小和使用標記,標記該記憶體節點的大小和是否使用等。空閒記憶體節點和已使用記憶體節點後面的資料域,尾節點沒有資料域。

本文通過分析動態記憶體模組的原始碼,幫助讀者掌握動態記憶體的使用。本文中所涉及的原始碼,以OpenHarmony LiteOS-M核心為例,均可以在開源站點 https://gitee.com/openharmony/kernel_liteos_m 獲取 。接下來,我們看下動態記憶體的結構體,動態記憶體初始化,動態記憶體常用操作的原始碼。

1、動態記憶體結構體定義和常用巨集定義

1.1 動態記憶體結構體定義

動態記憶體的結構體有動態記憶體池資訊結構體OsMemPoolInfo,動態記憶體池頭結構體OsMemPoolHead、動態記憶體節點頭結構體OsMemNodeHead,已使用記憶體節點結構體OsMemUsedNodeHead,空閒記憶體節點結構體OsMemFreeNodeHead。這些結構體定義在檔案kernel\src\mm\los_memory.c中,下文會結合上文的動態記憶體管理結構示意圖對各個結構體的成員變數進行說明。

1.1.1 動態記憶體池池頭相關結構體

動態記憶體池資訊結構體OsMemPoolInfo維護記憶體池的開始地址和大小資訊。三個主要的成員是記憶體池開始地址.pool,記憶體池大小.poolSize和記憶體值屬性.attr。如果開啟巨集LOSCFG_MEM_WATERLINE,還會維護記憶體池的水線數值。

struct OsMemPoolInfo {
    VOID *pool;               /* 記憶體池的記憶體開始地址 */
    UINT32 totalSize;         /* 記憶體池總大小 */
    UINT32 attr;              /* 記憶體池屬性 */
#if (LOSCFG_MEM_WATERLINE == 1)
    UINT32 waterLine;         /* 記憶體池中記憶體最大使用值 */
    UINT32 curUsedSize;       /* 記憶體池中當前已使用的大小 */
#endif
};

動態記憶體池頭結構體OsMemPoolHead原始碼如下,除了動態記憶體池資訊結構體struct OsMemPoolInfo info,還維護2個陣列,一個是空閒記憶體連結串列點陣圖陣列freeListBitmap[],一個是空閒記憶體連結串列陣列freeList[]。巨集定義OS_MEM_BITMAP_WORDS和OS_MEM_FREE_LIST_COUNT後文會介紹。

struct OsMemPoolHead {
    struct OsMemPoolInfo info;
    UINT32 freeListBitmap[OS_MEM_BITMAP_WORDS];
    struct OsMemFreeNodeHead *freeList[OS_MEM_FREE_LIST_COUNT];
#if (LOSCFG_MEM_MUL_POOL == 1)
    VOID *nextPool;
#endif
};

1.1.2 動態記憶體池記憶體節點相關結構體

先看下動態記憶體節點頭結構體OsMemNodeHead的定義,⑴處如果開啟記憶體節點完整性檢查的巨集LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK,會維護魔術字.magic進行校驗。⑵處如果開啟記憶體洩漏檢查的巨集,會維護連結暫存器陣列linkReg[]。⑶處的成員變數是個指標組合體,記憶體池中的每個記憶體節點頭維護指標執行上一個記憶體節點。⑷處維護記憶體節點的大小和標記資訊。

struct OsMemNodeHead {
  #if (LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK == 1)
⑴    UINT32 magic;
  #endif
  #if (LOSCFG_MEM_LEAKCHECK == 1)
⑵    UINTPTR linkReg[LOSCFG_MEM_RECORD_LR_CNT];
  #endif
      union {
          struct OsMemNodeHead *prev; /* The prev is used for current node points to the previous node */
          struct OsMemNodeHead *next; /* The next is used for sentinel node points to the expand node */
⑶     } ptr;
 #if (LOSCFG_MEM_FREE_BY_TASKID == 1)
⑷    UINT32 taskID : 6;
      UINT32 sizeAndFlag : 26;
  #else
      UINT32 sizeAndFlag;
  #endif
  };

接著看下已使用記憶體節點結構體OsMemUsedNodeHead,該結構體比較簡單,直接以動態記憶體節點頭結構體OsMemNodeHead作為唯一的成員。

struct OsMemUsedNodeHead {
    struct OsMemNodeHead header;
};

我們再看下空閒記憶體節點結構體OsMemFreeNodeHead,除了動態記憶體節點頭結構體OsMemNodeHead成員,還包含2個指標分別指向上一個和下一個空閒記憶體節點。

struct OsMemFreeNodeHead {
    struct OsMemNodeHead header;
    struct OsMemFreeNodeHead *prev;
    struct OsMemFreeNodeHead *next;
};

1.2 動態記憶體核心演算法相關的巨集和函式

動態記憶體中還提供了一些和TLSF演算法相關的巨集定義和行內函數,這些巨集非常重要,在分析原始碼前需要熟悉下這些巨集的定義。可以結合上文的動態記憶體核心演算法框圖進行學習。⑴處的巨集對處於[2^n,2^(n+1)],其中(n=7,8,…30)區間的大記憶體塊進行2^3=8等分。⑵處的巨集,定義處於[4,127]區間的小記憶體塊劃分為31個,即4,8,12,…,124。⑶處定義小記憶體的上界值,考慮記憶體對齊和粒度,最大值只能取到124。

⑷處的巨集表示處於[2^n,2^(n+1)],其中(n=7,8,…30)區間的大記憶體分為24個小區間,其中n=7 就是⑺處定義的巨集OS_MEM_LARGE_START_BUCKET。⑻處對應空閒記憶體連結串列的長度。⑼處是空閒連結串列點陣圖陣列的長度,31個小記憶體使用1個點陣圖字,所以需要加1。⑽處定義點陣圖掩碼,每個點陣圖字是32位無符號整數。

繼續看下行內函數。⑾處函式查詢點陣圖字中的第一個1的位元位,這個實現的功能類似內建函式__builtin_ctz。該函式用於獲取空閒記憶體連結串列對應的點陣圖字中,第一個掛載著空閒記憶體塊的空閒記憶體連結串列。⑿處獲取點陣圖字中的最後一個1的位元位,(從32位二進位制數值從左到右依次第0,1,…,31位)。⒀處函式名稱中的Log是對數英文logarithm的縮寫,函式用於計算以2為底的對數的整數部分。⒁處獲取記憶體區間的大小級別編號,對於小於128位元組的,有31個級別,對處於[2^n,2^(n+1)],其中(n=7,8,…30)區間的記憶體,有24個級別。⒂處根據記憶體大小,記憶體區間一級編號獲取獲取二級小區間的編號,對處於[2^n,2^(n+1)],其中(n=7,8,…30)區間的記憶體,有8個二級小區間。

    /* The following is the macro definition and interface implementation related to the TLSF. */

    /* Supposing a Second Level Index: SLI = 3. */#define OS_MEM_SLI                      3
    /* Giving 1 free list for each small bucket: 4, 8, 12, up to 124. */#define OS_MEM_SMALL_BUCKET_COUNT       31#define OS_MEM_SMALL_BUCKET_MAX_SIZE    128
    /* Giving 2^OS_MEM_SLI free lists for each large bucket. */#define OS_MEM_LARGE_BUCKET_COUNT       24
    /* OS_MEM_SMALL_BUCKET_MAX_SIZE to the power of 2 is 7. */#define OS_MEM_LARGE_START_BUCKET       7

    /* The count of free list. */#define OS_MEM_FREE_LIST_COUNT  (OS_MEM_SMALL_BUCKET_COUNT + (OS_MEM_LARGE_BUCKET_COUNT << OS_MEM_SLI))
    /* The bitmap is used to indicate whether the free list is empty, 1: not empty, 0: empty. */#define OS_MEM_BITMAP_WORDS     ((OS_MEM_FREE_LIST_COUNT >> 5) + 1)#define OS_MEM_BITMAP_MASK 0x1FU

    /* Used to find the first bit of 1 in bitmap. */
⑾  STATIC INLINE UINT16 OsMemFFS(UINT32 bitmap)
    {
        bitmap &= ~bitmap + 1;
        return (OS_MEM_BITMAP_MASK - CLZ(bitmap));
    }

    /* Used to find the last bit of 1 in bitmap. */
⑿  STATIC INLINE UINT16 OsMemFLS(UINT32 bitmap)
    {
        return (OS_MEM_BITMAP_MASK - CLZ(bitmap));
    }

⒀  STATIC INLINE UINT32 OsMemLog2(UINT32 size)
    {
        return (size > 0) ? OsMemFLS(size) : 0;
    }

    /* Get the first level: f = log2(size). */
⒁  STATIC INLINE UINT32 OsMemFlGet(UINT32 size)
    {
        if (size < OS_MEM_SMALL_BUCKET_MAX_SIZE) {
            return ((size >> 2) - 1); /* 2: The small bucket setup is 4. */
        }
        return (OsMemLog2(size) - OS_MEM_LARGE_START_BUCKET + OS_MEM_SMALL_BUCKET_COUNT);
    }

    /* Get the second level: s = (size - 2^f) * 2^SLI / 2^f. */
⒂  STATIC INLINE UINT32 OsMemSlGet(UINT32 size, UINT32 fl)
    {
        if ((fl < OS_MEM_SMALL_BUCKET_COUNT) || (size < OS_MEM_SMALL_BUCKET_MAX_SIZE)) {
            PRINT_ERR("fl or size is too small, fl = %u, size = %u\n", fl, size);
            return 0;
        }

        UINT32 sl = (size << OS_MEM_SLI) >> (fl - OS_MEM_SMALL_BUCKET_COUNT + OS_MEM_LARGE_START_BUCKET);
        return (sl - (1 << OS_MEM_SLI));
    }

2、動態記憶體常用操作

動態記憶體管理模組為使用者提供初始化和刪除記憶體池、申請、釋放動態記憶體等操作,我們來分析下介面的原始碼。在分析下記憶體操作介面之前,我們先看下一下常用的內部介面。

2.1 動態記憶體內部介面

2.1.1 設定和清理空閒記憶體連結串列標記位

⑴處函式OsMemSetFreeListBit需要2個引數,一個是記憶體池池頭head,一個是空閒記憶體連結串列索引index。當空閒記憶體連結串列上掛載有空閒記憶體塊時,點陣圖字相應的位需要設定為1。⑴處函式OsMemClearFreeListBit做相反的操作,當空閒記憶體連結串列上不再掛載空閒記憶體塊時,需要對應的位元位清零。

  STATIC INLINE VOID OsMemSetFreeListBit(struct OsMemPoolHead *head, UINT32 index)
  {
⑴    head->freeListBitmap[index >> 5] |= 1U << (index & 0x1f);
  }

  STATIC INLINE VOID OsMemClearFreeListBit(struct OsMemPoolHead *head, UINT32 index)
  {
⑵    head->freeListBitmap[index >> 5] &= ~(1U << (index & 0x1f));
  }

2.1.2 合併記憶體節點

函式VOID OsMemMergeNode(struct OsMemNodeHead *node)用於合併給定節點struct OsMemNodeHead *node和它前一個空閒節點。⑴處把前一個節點的大小加上要合入節點的大小。⑵處獲取給定節點的下一個節點,然後執行⑶把它的前一個節點指向給定節點的前一個節點,完成節點的合併。其中巨集OS_MEM_NODE_GET_LAST_FLAG用於判斷是否最後一個節點,預設為0,可以自行檢視下該巨集的定義。

STATIC INLINE VOID OsMemMergeNode(struct OsMemNodeHead *node)
{
    struct OsMemNodeHead *nextNode = NULL;

⑴  node->ptr.prev->sizeAndFlag += node->sizeAndFlag;
⑵  nextNode = (struct OsMemNodeHead *)((UINTPTR)node + node->sizeAndFlag);
    if (!OS_MEM_NODE_GET_LAST_FLAG(nextNode->sizeAndFlag)) {
⑶      nextNode->ptr.prev = node->ptr.prev;
    }
}

2.1.3 分割記憶體節點

函式VOID OsMemSplitNode(VOID *pool, struct OsMemNodeHead *allocNode, UINT32 allocSize)用於分割記憶體節點,需要三個引數。VOID *pool是記憶體池起始地址,struct OsMemNodeHead *allocNode表示從該記憶體節點分配出需要的記憶體,UINT32 allocSize是需要分配的記憶體大小。分割之後剩餘的部分,如果下一個節點是空閒節點,則合併一起。分割剩餘的節點會掛載到空閒記憶體連結串列上。

⑴處表示newFreeNode是分配之後剩餘的空閒記憶體節點,設定它的上一個節點為分配的節點,並設定剩餘記憶體大小。⑵處調整分配記憶體的大小,⑶處獲取下一個節點,然後執行⑷下一個節點的前一個節點設定為新的空閒節點newFreeNode。⑸處判斷下一個節點是否被使用,如果沒有使用,則把下一個節點從連結串列中刪除,然後和空閒節點newFreeNode合併。⑹處分割剩餘的空閒記憶體節點掛載到連結串列上。

STATIC INLINE VOID OsMemSplitNode(VOID *pool, struct OsMemNodeHead *allocNode, UINT32 allocSize)
{
    struct OsMemFreeNodeHead *newFreeNode = NULL;
    struct OsMemNodeHead *nextNode = NULL;

⑴  newFreeNode = (struct OsMemFreeNodeHead *)(VOID *)((UINT8 *)allocNode + allocSize);
    newFreeNode->header.ptr.prev = allocNode;
    newFreeNode->header.sizeAndFlag = allocNode->sizeAndFlag - allocSize;
⑵  allocNode->sizeAndFlag = allocSize;
⑶  nextNode = OS_MEM_NEXT_NODE(&newFreeNode->header);
    if (!OS_MEM_NODE_GET_LAST_FLAG(nextNode->sizeAndFlag)) {
⑷      nextNode->ptr.prev = &newFreeNode->header;
        if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->sizeAndFlag)) {
⑸          OsMemFreeNodeDelete(pool, (struct OsMemFreeNodeHead *)nextNode);
            OsMemMergeNode(nextNode);
        }
    }

⑹  OsMemFreeNodeAdd(pool, newFreeNode);
}

2.1.4 重新申請記憶體

OsMemReAllocSmaller()函式用於從一個大的記憶體塊裡重新申請一個較小的記憶體,他需要的4個引數分別是:VOID *pool是記憶體池起始地址,UINT32 allocSize是重新申請的記憶體的大小,struct OsMemNodeHead *node是當前需要重新分配記憶體的記憶體節點,UINT32 nodeSize是當前節點的大小。⑴設定記憶體節點selfNode.sizeAndFlag為去除標記後的實際大小,⑵按需分割節點,⑶分割後的節點設定已使用標記,完成完成申請記憶體。

STATIC INLINE VOID OsMemReAllocSmaller(VOID *pool, UINT32 allocSize, struct OsMemNodeHead *node, UINT32 nodeSize)
{
#if (LOSCFG_MEM_WATERLINE == 1)
    struct OsMemPoolHead *poolInfo = (struct OsMemPoolHead *)pool;
#endif
⑴  node->sizeAndFlag = nodeSize;
    if ((allocSize + OS_MEM_MIN_LEFT_SIZE) <= nodeSize) {
⑵       OsMemSplitNode(pool, node, allocSize);
#if (LOSCFG_MEM_WATERLINE == 1)
        poolInfo->info.curUsedSize -= nodeSize - allocSize;
#endif
    }
⑶  OS_MEM_NODE_SET_USED_FLAG(node->sizeAndFlag);
#if (LOSCFG_MEM_LEAKCHECK == 1)
    OsMemLinkRegisterRecord(node);
#endif
}

2.1.5 合併節點重新申請記憶體

最後,再來看下函式函式OsMemMergeNodeForReAllocBigger(),用於合併記憶體節點,重新分配更大的記憶體空間。它需要5個引數,VOID *pool是記憶體池起始地址,UINT32 allocSize是重新申請的記憶體的大小,struct OsMemNodeHead *node是當前需要重新分配記憶體的記憶體節點,UINT32 nodeSize是當前節點的大小,struct OsMemNodeHead *nextNode是下一個記憶體節點。⑴處設定記憶體節點的大小為去除標記後的實際大小,⑵把下一個節點從連結串列上刪除,然後合併節點。⑶處如果合併後的節點大小超過需要重新分配的大小,則分割節點。⑷處把申請的記憶體節點標記為已使用,完成完成申請記憶體

STATIC INLINE VOID OsMemMergeNodeForReAllocBigger(VOID *pool, UINT32 allocSize, struct OsMemNodeHead *node,
                                                  UINT32 nodeSize, struct OsMemNodeHead *nextNode)
{
⑴  node->sizeAndFlag = nodeSize;
⑵  OsMemFreeNodeDelete(pool, (struct OsMemFreeNodeHead *)nextNode);
    OsMemMergeNode(nextNode);
    if ((allocSize + OS_MEM_MIN_LEFT_SIZE) <= node->sizeAndFlag) {
⑶       OsMemSplitNode(pool, node, allocSize);
    }
⑷  OS_MEM_NODE_SET_USED_FLAG(node->sizeAndFlag);
    OsMemWaterUsedRecord((struct OsMemPoolHead *)pool, node->sizeAndFlag - nodeSize);
#if (LOSCFG_MEM_LEAKCHECK == 1)
    OsMemLinkRegisterRecord(node);
#endif
}

2.1.6 空閒記憶體連結串列相關操作

動態記憶體提供了針對空閒記憶體連結串列的幾個操作,我們依次分析下這些操作的程式碼。首先看下函式OsMemFreeListIndexGet,根據記憶體節點大小獲取空閒記憶體連結串列的索引。⑴處先獲取一級索引,⑵處獲取二級索引,然後計算空閒連結串列的索引並返回。

STATIC INLINE UINT32 OsMemFreeListIndexGet(UINT32 size)
{
⑴  UINT32 fl = OsMemFlGet(size);
    if (fl < OS_MEM_SMALL_BUCKET_COUNT) {
        return fl;
    }

⑵  UINT32 sl = OsMemSlGet(size, fl);
    return (OS_MEM_SMALL_BUCKET_COUNT + ((fl - OS_MEM_SMALL_BUCKET_COUNT) << OS_MEM_SLI) + sl);
}

接著看下函式OsMemListAdd,如何把空閒記憶體節點插入空閒記憶體連結串列。⑴處獲取空閒連結串列的第一個節點,如果節點不為空,則把這個節點的前驅節點設定為待插入節點node。⑵處設定待插入節點的前驅、後繼節點,然後把該節點賦值給空閒連結串列pool->freeList[listIndex]。最後執行⑶處程式碼,把設定空閒連結串列點陣圖字,並設定魔術字。

STATIC INLINE VOID OsMemListAdd(struct OsMemPoolHead *pool, UINT32 listIndex, struct OsMemFreeNodeHead *node)
{
⑴  struct OsMemFreeNodeHead *firstNode = pool->freeList[listIndex];
    if (firstNode != NULL) {
        firstNode->prev = node;
    }
⑵  node->prev = NULL;
    node->next = firstNode;
    pool->freeList[listIndex] = node;
⑶  OsMemSetFreeListBit(pool, listIndex);
    OS_MEM_SET_MAGIC(&node->header);
}

最後,分析下函式OsMemListDelete如何從空閒記憶體連結串列刪除指定的空閒記憶體節點。⑴處如果刪除的節點是空閒記憶體連結串列的第一個節點,則需要把空閒連結串列執行待刪除節點的下一個節點。如果下一個節點為空,需要執行⑵清除空閒連結串列的點陣圖字。否則執行⑶把下一個節點的前驅節點設定為空。如果待刪除節點不是空閒連結串列的第一個節點,執行⑷把待刪除節點的前驅節點的後續節點設定為待刪除節點的後繼節點。如果待刪除節點不為最後一個節點,需要執行⑸把待刪除節點的後繼節點的前驅節點設定為待刪除節點的前驅節點。最後需要設定下魔術字。

STATIC INLINE VOID OsMemListDelete(struct OsMemPoolHead *pool, UINT32 listIndex, struct OsMemFreeNodeHead *node)
{
⑴  if (node == pool->freeList[listIndex]) {
        pool->freeList[listIndex] = node->next;
        if (node->next == NULL) {
⑵          OsMemClearFreeListBit(pool, listIndex);
        } else {
⑶          node->next->prev = NULL;
        }
    } else {
⑷      node->prev->next = node->next;
        if (node->next != NULL) {
⑸          node->next->prev = node->prev;
        }
    }
    OS_MEM_SET_MAGIC(&node->header);
}

2.1.7 空閒記憶體節點相關操作

動態記憶體提供了針對空閒記憶體的幾個操作,如OsMemFreeNodeAdd、OsMemFreeNodeDelete、OsMemFreeNodeGet。

函式OsMemFreeNodeAdd用於把一個空閒記憶體節點加入相應的空閒記憶體連結串列上。⑴處呼叫函式獲取空閒記憶體連結串列的索引,然後執行⑵把空閒記憶體節點加入空閒連結串列。

STATIC INLINE VOID OsMemFreeNodeAdd(VOID *pool, struct OsMemFreeNodeHead *node)
{
⑴  UINT32 index = OsMemFreeListIndexGet(node->header.sizeAndFlag);
    if (index >= OS_MEM_FREE_LIST_COUNT) {
        LOS_Panic("The index of free lists is error, index = %u\n", index);
    }
⑵  OsMemListAdd(pool, index, node);
}

函式OsMemFreeNodeDelete用於把一個空閒記憶體節點從相應的空閒記憶體連結串列上刪除。程式碼較簡單,獲取空閒記憶體連結串列的索引,然後呼叫函式OsMemListDelete進行刪除。

STATIC INLINE VOID OsMemFreeNodeDelete(VOID *pool, struct OsMemFreeNodeHead *node)
{
    UINT32 index = OsMemFreeListIndexGet(node->header.sizeAndFlag);
    OsMemListDelete(pool, index, node);
}

函式OsMemFreeNodeGet根據記憶體池地址和需要的記憶體大小獲取滿足大小條件的空閒記憶體塊。⑴處呼叫函式獲取滿足大小條件的記憶體塊,然後執行⑵把獲取到的記憶體塊從空閒記憶體連結串列刪除,返回記憶體節點地址。

STATIC INLINE struct OsMemNodeHead *OsMemFreeNodeGet(VOID *pool, UINT32 size)
{
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    UINT32 index;
⑴  struct OsMemFreeNodeHead *firstNode = OsMemFindNextSuitableBlock(pool, size, &index);
    if (firstNode == NULL) {
        return NULL;
    }

⑵  OsMemListDelete(poolHead, index, firstNode);

    return &firstNode->header;
}

最後,分析下函式OsMemFindNextSuitableBlock。⑴處根據需要的記憶體塊大小獲取一級區間編號,如果申請的記憶體處於[4,127]區間,執行⑵處記錄空閒記憶體連結串列索引。如果需要申請的是大記憶體,執行⑶處程式碼。先獲取二級區間索引,然後計算出空閒記憶體連結串列的索引值index。這樣計算出來的空閒記憶體連結串列下可能並沒有掛載空閒記憶體塊,呼叫⑷處函式OsMemNotEmptyIndexGet獲取掛載空閒記憶體塊的空閒記憶體連結串列索引值。如果成功獲取到滿足大小的空閒記憶體塊,返回空閒連結串列索引值,否則繼續執行後續程式碼。⑹處對空閒連結串列點陣圖字進行遍歷,迴圈中的自增變數index對應一級區間編號。如果點陣圖字不為空,執行⑺獲取這個點陣圖字對應的最大的空閒記憶體連結串列的索引。

如果執行到⑻處,說明沒有匹配到合適的記憶體塊,返回空指標。⑼處表示存在滿足大小的空閒記憶體連結串列,呼叫函式OsMemFindCurSuitableBlock獲取合適的記憶體塊並返回。⑽處標籤表示獲取到合適的空閒記憶體連結串列索引,返回空閒記憶體連結串列。

STATIC INLINE struct OsMemFreeNodeHead *OsMemFindNextSuitableBlock(VOID *pool, UINT32 size, UINT32 *outIndex)
{
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
⑴  UINT32 fl = OsMemFlGet(size);
    UINT32 sl;
    UINT32 index, tmp;
    UINT32 curIndex = OS_MEM_FREE_LIST_COUNT;
    UINT32 mask;

    do {
        if (fl < OS_MEM_SMALL_BUCKET_COUNT) {
⑵          index = fl;
        } else {
⑶          sl = OsMemSlGet(size, fl);
            curIndex = ((fl - OS_MEM_SMALL_BUCKET_COUNT) << OS_MEM_SLI) + sl + OS_MEM_SMALL_BUCKET_COUNT;
            index = curIndex + 1;
        }

⑷      tmp = OsMemNotEmptyIndexGet(poolHead, index);
        if (tmp != OS_MEM_FREE_LIST_COUNT) {
⑸          index = tmp;
            goto DONE;
        }

⑹      for (index = LOS_Align(index + 1, 32); index < OS_MEM_FREE_LIST_COUNT; index += 32) {
            mask = poolHead->freeListBitmap[index >> 5]; /* 5: Divide by 32 to calculate the index of the bitmap array. */
            if (mask != 0) {
⑺              index = OsMemFFS(mask) + index;
                goto DONE;
            }
        }
    } while (0);

⑻  if (curIndex == OS_MEM_FREE_LIST_COUNT) {
        return NULL;
    }

⑼  *outIndex = curIndex;
    return OsMemFindCurSuitableBlock(poolHead, curIndex, size);
DONE:
    *outIndex = index;
⑽  return poolHead->freeList[index];
}

我們再詳細分析下函式OsMemNotEmptyIndexGet的原始碼。⑴處根據空閒記憶體連結串列索引獲取點陣圖字,⑵處判斷空閒記憶體連結串列索引對應的一級記憶體區間對應的二級小記憶體區間是否存在滿足條件的空閒記憶體塊。其中index & OS_MEM_BITMAP_MASK對索引只取低5位後,可以把索引值和點陣圖字中的位元位關聯起來,比如index為39時,index & OS_MEM_BITMAP_MASK等於7,對應點陣圖字的第7位。表示式~((1 << (index & OS_MEM_BITMAP_MASK)) - 1)則用於表示大於空閒記憶體連結串列索引index的索引值對應的點陣圖字。⑵處的語句執行後,mask就表示空閒連結串列索引值大於index的連結串列索引對應的點陣圖字的值。當mask不為0時,表示存在滿足記憶體大小的空閒記憶體塊,則執行⑶處程式碼,其中OsMemFFS(mask)獲取點陣圖字中第一個為1的位元位位數,該位對應著掛載空閒記憶體塊的連結串列。(index & ~OS_MEM_BITMAP_MASK)對應連結串列索引的高位,加上點陣圖字位數就計算出掛載著滿足申請條件的空閒記憶體連結串列的索引值。

STATIC INLINE UINT32 OsMemNotEmptyIndexGet(struct OsMemPoolHead *poolHead, UINT32 index)
{
⑴  UINT32 mask = poolHead->freeListBitmap[index >> 5]; /* 5: Divide by 32 to calculate the index of the bitmap array. */
⑵  mask &= ~((1 << (index & OS_MEM_BITMAP_MASK)) - 1);
    if (mask != 0) {
⑶      index = OsMemFFS(mask) + (index & ~OS_MEM_BITMAP_MASK);
        return index;
    }

    return OS_MEM_FREE_LIST_COUNT;
}

最後,再看下函式OsMemFindCurSuitableBlock。⑴處迴圈遍歷空閒記憶體連結串列上掛載的記憶體塊,如果遍歷到的記憶體塊大小大於需要的大小,則執行⑵返回該空閒記憶體塊。否則返回空指標。

STATIC INLINE struct OsMemFreeNodeHead *OsMemFindCurSuitableBlock(struct OsMemPoolHead *poolHead,
                                        UINT32 index, UINT32 size)
{
    struct OsMemFreeNodeHead *node = NULL;

⑴  for (node = poolHead->freeList[index]; node != NULL; node = node->next) {
        if (node->header.sizeAndFlag >= size) {
⑵           return node;
        }
    }

    return NULL;
}

2.2 初始化動態記憶體池

我們分析下初始化動態記憶體池函式UINT32 LOS_MemInit(VOID *pool, UINT32 size)的程式碼。我們先看看函式引數,VOID *pool是動態記憶體池的起始地址,UINT32 size是初始化的動態記憶體池的總大小,size需要小於等於*pool開始的記憶體區域的大小,否則會影響後面的記憶體區域,還需要大於動態記憶體池的最小值OS_MEM_MIN_POOL_SIZE。[pool, pool + size]不能和其他記憶體池衝突。

我們看下程式碼,⑴處對傳入引數進行校驗,⑵處對傳入引數進行是否記憶體對齊校驗,如果沒有記憶體對齊會返回錯誤碼。⑶處呼叫函式OsMemPoolInit()進行記憶體池初始化,這是初始化記憶體的核心函式。⑷處開啟巨集LOSCFG_MEM_MUL_POOL多記憶體池支援時,才會執行。

UINT32 LOS_MemInit(VOID *pool, UINT32 size)
{
⑴  if ((pool == NULL) || (size <= OS_MEM_MIN_POOL_SIZE)) {
        return OS_ERROR;
    }

⑵  if (((UINTPTR)pool & (OS_MEM_ALIGN_SIZE - 1)) || \
        (size & (OS_MEM_ALIGN_SIZE - 1))) {
        PRINT_ERR("LiteOS heap memory address or size configured not aligned:address:0x%x,size:0x%x, alignsize:%d\n", \
                  (UINTPTR)pool, size, OS_MEM_ALIGN_SIZE);
        return OS_ERROR;
    }

⑶  if (OsMemPoolInit(pool, size)) {
        return OS_ERROR;
    }

#if (LOSCFG_MEM_MUL_POOL == 1)if (OsMemPoolAdd(pool, size)) {
        (VOID)OsMemPoolDeinit(pool);
        return OS_ERROR;
    }
#endif

#if OS_MEM_TRACE
    LOS_TraceReg(LOS_TRACE_MEM_TIME, OsMemTimeTrace, LOS_TRACE_MEM_TIME_NAME, LOS_TRACE_ENABLE);
    LOS_TraceReg(LOS_TRACE_MEM_INFO, OsMemInfoTrace, LOS_TRACE_MEM_INFO_NAME, LOS_TRACE_ENABLE);
#endif

    OsHookCall(LOS_HOOK_TYPE_MEM_INIT, pool, size);

    return LOS_OK;
}

我們繼續看下函式OsMemPoolInit()。⑴處設定動態記憶體池資訊結構體struct OsMemPoolHead *poolHead的起始地址和大小,⑵處設定記憶體池屬性設定為鎖定、不可擴充套件。⑶處獲取記憶體池的第一個記憶體控制節點,然後設定它的大小,該節點大小等於記憶體池總大小減去記憶體池池頭大小和一個記憶體節點頭大小。然後再設定該記憶體節點的上一個節點為記憶體池的最後一個節點OS_MEM_END_NODE(pool, size)。

⑷處呼叫巨集給節點設定魔術字,然後把記憶體節點插入到空閒記憶體連結串列中。⑸處獲取記憶體池的尾節點,設定魔術字,然後執行⑹設定尾節點大小為0和設定上一個節點,並設定已使用標記。如果開啟調測巨集LOSCFG_MEM_WATERLINE,還會有些其他操作,自行閱讀即可。

STATIC UINT32 OsMemPoolInit(VOID *pool, UINT32 size)
{
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    struct OsMemNodeHead *newNode = NULL;
    struct OsMemNodeHead *endNode = NULL;

    (VOID)memset_s(poolHead, sizeof(struct OsMemPoolHead), 0, sizeof(struct OsMemPoolHead));

⑴  poolHead->info.pool = pool;
    poolHead->info.totalSize = size;
    poolHead->info.attr &= ~(OS_MEM_POOL_UNLOCK_ENABLE | OS_MEM_POOL_EXPAND_ENABLE); /* default attr: lock, not expand. */

⑶  newNode = OS_MEM_FIRST_NODE(pool);
    newNode->sizeAndFlag = (size - sizeof(struct OsMemPoolHead) - OS_MEM_NODE_HEAD_SIZE);
    newNode->ptr.prev = OS_MEM_END_NODE(pool, size);
⑷  OS_MEM_SET_MAGIC(newNode);
    OsMemFreeNodeAdd(pool, (struct OsMemFreeNodeHead *)newNode);

    /* The last mem node */
⑸  endNode = OS_MEM_END_NODE(pool, size);
    OS_MEM_SET_MAGIC(endNode);
#if OS_MEM_EXPAND_ENABLE
    endNode->ptr.next = NULL;
    OsMemSentinelNodeSet(endNode, NULL, 0);
#else
⑹  endNode->sizeAndFlag = 0;
    endNode->ptr.prev = newNode;
    OS_MEM_NODE_SET_USED_FLAG(endNode->sizeAndFlag);
#endif
#if (LOSCFG_MEM_WATERLINE == 1)
    poolHead->info.curUsedSize = sizeof(struct OsMemPoolHead) + OS_MEM_NODE_HEAD_SIZE;
    poolHead->info.waterLine = poolHead->info.curUsedSize;
#endif

    return LOS_OK;
}

2.3 申請動態記憶體

初始化動態記憶體池後,我們可以使用函式VOID *LOS_MemAlloc(VOID *pool, UINT32 size)來申請動態記憶體,下面分析下原始碼。

⑴處對引數進行校驗,記憶體池地址不能為空,申請的記憶體大小不能為0。⑵處判斷申請的記憶體大小是否已標記為使用或記憶體對齊。⑶處呼叫函式OsMemAlloc(poolHead, size, intSave)申請記憶體塊。

VOID *LOS_MemAlloc(VOID *pool, UINT32 size)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endifif ((pool == NULL) || (size == 0)) {
        return NULL;
    }

    if (size < OS_MEM_MIN_ALLOC_SIZE) {
        size = OS_MEM_MIN_ALLOC_SIZE;
    }

    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    VOID *ptr = NULL;
    UINT32 intSave;

    MEM_LOCK(poolHead, intSave);
    do {
⑵      if (OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size)) {
            break;
        }
⑶      ptr = OsMemAlloc(poolHead, size, intSave);
    } while (0);
    MEM_UNLOCK(poolHead, intSave);

#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_MALLOC, timeUsed);

    LOS_MEM_POOL_STATUS poolStatus = {0};
    (VOID)LOS_MemInfoGet(pool, &poolStatus);
    UINT8 fragment = 100 - poolStatus.maxFreeNodeSize * 100 / poolStatus.totalFreeSize; /* 100: percent denominator. */
    UINT8 usage = LOS_MemTotalUsedGet(pool) * 100 / LOS_MemPoolSizeGet(pool); /* 100: percent denominator. */
    LOS_Trace(LOS_TRACE_MEM_INFO, (UINTPTR)pool & MEM_POOL_ADDR_MASK, fragment, usage, poolStatus.totalFreeSize,
              poolStatus.maxFreeNodeSize, poolStatus.usedNodeNum, poolStatus.freeNodeNum);
#endif

    OsHookCall(LOS_HOOK_TYPE_MEM_ALLOC, pool, size);

    return ptr;
}

我們繼續分析函式OsMemAlloc()。⑴處對申請記憶體大小加上頭結點大小的和進行記憶體對齊,⑵處從空閒記憶體連結串列中獲取一個滿足申請大小的空閒記憶體塊,如果申請失敗,則列印錯誤資訊。⑶處如果找到的記憶體塊大於需要的記憶體大小,則執行分割操作。⑷處把已分配的記憶體節點標記為已使用,更新水線記錄。⑸返回記憶體塊的資料區的地址,這個是通過記憶體節點地址加1定位到資料區記憶體地址實現的。申請記憶體完成,呼叫申請記憶體的函式中可以使用申請的記憶體了。

STATIC INLINE VOID *OsMemAlloc(struct OsMemPoolHead *pool, UINT32 size, UINT32 intSave)
{
    struct OsMemNodeHead *allocNode = NULL;

#if (LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK == 1)
    if (OsMemAllocCheck(pool, intSave) == LOS_NOK) {
        return NULL;
    }
#endif

⑴  UINT32 allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);

#if OS_MEM_EXPAND_ENABLE
retry:
#endif
⑵  allocNode = OsMemFreeNodeGet(pool, allocSize);
    if (allocNode == NULL) {
#if OS_MEM_EXPAND_ENABLE
        if (pool->info.attr & OS_MEM_POOL_EXPAND_ENABLE) {
            INT32 ret = OsMemPoolExpand(pool, allocSize, intSave);
            if (ret == 0) {
                goto retry;
            }
        }
#endif
        PRINT_ERR("---------------------------------------------------"
                  "--------------------------------------------------------\n");
        MEM_UNLOCK(pool, intSave);
        OsMemInfoPrint(pool);
        MEM_LOCK(pool, intSave);
        PRINT_ERR("[%s] No suitable free block, require free node size: 0x%x\n", __FUNCTION__, allocSize);
        PRINT_ERR("----------------------------------------------------"
                  "-------------------------------------------------------\n");
        return NULL;
    }

⑶  if ((allocSize + OS_MEM_MIN_LEFT_SIZE) <= allocNode->sizeAndFlag) {
        OsMemSplitNode(pool, allocNode, allocSize);
    }

⑷  OS_MEM_NODE_SET_USED_FLAG(allocNode->sizeAndFlag);
    OsMemWaterUsedRecord(pool, OS_MEM_NODE_GET_SIZE(allocNode->sizeAndFlag));

#if (LOSCFG_MEM_LEAKCHECK == 1)
    OsMemLinkRegisterRecord(allocNode);
#endifreturn OsMemCreateUsedNode((VOID *)allocNode);
}

2.4 按指定位元組對齊申請動態記憶體

我們還可以使用函式VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary),從指定動態記憶體池中申請長度為size且地址按boundary位元組對齊的記憶體。該函式需要3個引數,VOID *pool為記憶體池起始地址,UINT32 size為需要申請的記憶體大小,UINT32 boundary記憶體對齊數值。當申請記憶體後得到的記憶體地址VOID *ptr,對齊後的記憶體地址為VOID *alignedPtr,二者的偏移值使用UINT32 gapSize儲存。因為已經按OS_MEM_ALIGN_SIZE記憶體對齊了,最大偏移值為boundary - OS_MEM_ALIGN_SIZE。下面分析下原始碼。

⑴處對引數進行校驗,記憶體池地址不能為空,申請的記憶體大小不能為0,對齊位元組boundary不能為0,還需要是2的冪。申請的記憶體大小必須大於最小的申請值OS_MEM_MIN_ALLOC_SIZE。⑵處校驗下對齊記憶體後是否會資料溢位。⑶處計算對齊後需要申請的記憶體大小,然後判斷記憶體大小數值沒有已使用或已對齊標記。⑷處呼叫函式申請到記憶體VOID *ptr,然後計算出對齊的記憶體地址VOID *alignedPtr,如果二者相等則返回。⑸處計算出對齊記憶體的偏移值,⑹處獲取申請到的記憶體的頭節點,設定已對齊標記。⑺對偏移值設定對齊標記,然後把偏移值儲存在記憶體VOID *alignedPtr的前4個位元組裡。⑻處重新定向要返回的指標,完成申請對齊的記憶體。

VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endif

    UINT32 gapSize;

⑴  if ((pool == NULL) || (size == 0) || (boundary == 0) || !OS_MEM_IS_POW_TWO(boundary) ||
        !OS_MEM_IS_ALIGNED(boundary, sizeof(VOID *))) {
        return NULL;
    }

    if (size < OS_MEM_MIN_ALLOC_SIZE) {
        size = OS_MEM_MIN_ALLOC_SIZE;
    }

⑵  if ((boundary - sizeof(gapSize)) > ((UINT32)(-1) - size)) {
        return NULL;
    }

⑶  UINT32 useSize = (size + boundary) - sizeof(gapSize);
    if (OS_MEM_NODE_GET_USED_FLAG(useSize) || OS_MEM_NODE_GET_ALIGNED_FLAG(useSize)) {
        return NULL;
    }

    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    UINT32 intSave;
    VOID *ptr = NULL;
    VOID *alignedPtr = NULL;

    MEM_LOCK(poolHead, intSave);
    do {
⑷      ptr = OsMemAlloc(pool, useSize, intSave);
        alignedPtr = (VOID *)OS_MEM_ALIGN(ptr, boundary);
        if (ptr == alignedPtr) {
            break;
        }

        /* store gapSize in address (ptr - 4), it will be checked while free */
⑸      gapSize = (UINT32)((UINTPTR)alignedPtr - (UINTPTR)ptr);
⑹      struct OsMemUsedNodeHead *allocNode = (struct OsMemUsedNodeHead *)ptr - 1;
        OS_MEM_NODE_SET_ALIGNED_FLAG(allocNode->header.sizeAndFlag);
⑺      OS_MEM_SET_GAPSIZE_ALIGNED_FLAG(gapSize);
        *(UINT32 *)((UINTPTR)alignedPtr - sizeof(gapSize)) = gapSize;
⑻      ptr = alignedPtr;
    } while (0);
    MEM_UNLOCK(poolHead, intSave);

#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_MEMALIGN, timeUsed);
#endif

    OsHookCall(LOS_HOOK_TYPE_MEM_ALLOCALIGN, pool, size, boundary);

    return ptr;
}

2.5 釋放動態記憶體

對申請的記憶體塊使用完畢,我們可以使用函式UINT32 LOS_MemFree(VOID *pool, VOID *ptr)來釋放動態態記憶體,需要2個引數,VOID *pool是初始化過的動態記憶體池地址。VOID *ptr是需要釋放的動態記憶體塊的資料區的起始地址,注意這個不是記憶體控制節點的地址。下面分析下原始碼,⑴處對傳入的引數先進行校驗。⑵處獲取校準記憶體對齊後的真實的記憶體地址,然後獲取記憶體節點頭地址。⑶處呼叫函式OsMemFree(pool, ptr)完成記憶體的釋放。

UINT32 LOS_MemFree(VOID *pool, VOID *ptr)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endifif ((pool == NULL) || (ptr == NULL) || !OS_MEM_IS_ALIGNED(pool, sizeof(VOID *)) ||
        !OS_MEM_IS_ALIGNED(ptr, sizeof(VOID *))) {
        return LOS_NOK;
    }

    UINT32 ret = LOS_NOK;
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    struct OsMemNodeHead *node = NULL;
    UINT32 intSave;

    MEM_LOCK(poolHead, intSave);
    do {
⑵      ptr = OsGetRealPtr(pool, ptr);
        if (ptr == NULL) {
            break;
        }
        node = (struct OsMemNodeHead *)((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE);
⑶      ret = OsMemFree(poolHead, node);
    } while (0);
    MEM_UNLOCK(poolHead, intSave);

#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_FREE, timeUsed);
#endif

    OsHookCall(LOS_HOOK_TYPE_MEM_FREE, pool, ptr);

    return ret;
}

我們回過頭來,繼續看下函式OsGetRealPtr()。⑴獲取記憶體對齊的偏移值,⑵如果偏移值同時標記為已使用和已對齊,則返回錯誤。⑶如果偏移值標記為已對齊,則執行⑷去除對齊標記,獲取不帶標記的偏移值。然後執行⑸,獲取記憶體對齊之前的資料區記憶體地址。

STATIC INLINE VOID *OsGetRealPtr(const VOID *pool, VOID *ptr)
{
    VOID *realPtr = ptr;
⑴  UINT32 gapSize = *((UINT32 *)((UINTPTR)ptr - sizeof(UINT32)));

⑵  if (OS_MEM_GAPSIZE_CHECK(gapSize)) {
        PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
        return NULL;
    }

⑶  if (OS_MEM_GET_GAPSIZE_ALIGNED_FLAG(gapSize)) {
⑷      gapSize = OS_MEM_GET_ALIGNED_GAPSIZE(gapSize);
        if ((gapSize & (OS_MEM_ALIGN_SIZE - 1)) ||
            (gapSize > ((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE - (UINTPTR)pool))) {
            PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
            return NULL;
        }
⑸      realPtr = (VOID *)((UINTPTR)ptr - (UINTPTR)gapSize);
    }
    return realPtr;
}

2.6 重新申請動態記憶體

我們還可以使用函式VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size),按指定size大小重新分配記憶體塊,並將原記憶體塊內容拷貝到新記憶體塊。如果新記憶體塊申請成功,則釋放原記憶體塊。該函式需要3個引數,VOID *pool為記憶體池起始地址,VOID *ptr為之前申請的記憶體地址,UINT32 size為重新申請的記憶體大小。返回值為新記憶體塊地址,或者返回NULL。下面分析下原始碼。

⑴處對引數進行校驗,記憶體池地址不能為空,記憶體大小不能含有已使用、已對齊標記。⑵處如果傳入的記憶體地址為空,則等價於LOS_MemAlloc()函式。⑶如果傳入size為0,等價於函式LOS_MemFree()。⑷處保證申請的記憶體塊大小至少為系統允許的最小值OS_MEM_MIN_ALLOC_SIZE。⑸處獲取記憶體對齊之前的記憶體地址,上文已分析該函式OsGetRealPtr()。⑹處由資料域記憶體地址計算出記憶體控制節點node的記憶體地址,然後執行⑺處函式重新申請記憶體。

VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endifif ((pool == NULL) || OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size)) {
        return NULL;
    }

    OsHookCall(LOS_HOOK_TYPE_MEM_REALLOC, pool, ptr, size);

⑵  if (ptr == NULL) {
        return LOS_MemAlloc(pool, size);
    }

⑶  if (size == 0) {
        (VOID)LOS_MemFree(pool, ptr);
        return NULL;
    }

⑷  if (size < OS_MEM_MIN_ALLOC_SIZE) {
        size = OS_MEM_MIN_ALLOC_SIZE;
    }

    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    struct OsMemNodeHead *node = NULL;
    VOID *newPtr = NULL;
    UINT32 intSave;

    MEM_LOCK(poolHead, intSave);
    do {
⑸      ptr = OsGetRealPtr(pool, ptr);
        if (ptr == NULL) {
            break;
        }

⑹      node = (struct OsMemNodeHead *)((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE);
        if (OsMemCheckUsedNode(pool, node) != LOS_OK) {
            break;
        }

⑺      newPtr = OsMemRealloc(pool, ptr, node, size, intSave);
    } while (0);
    MEM_UNLOCK(poolHead, intSave);

#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_REALLOC, timeUsed);
#endif

    return newPtr;
}

繼續分析下函式OsMemRealloc。⑴處處理重新申請的記憶體小於等於現有的記憶體的情況,需要呼叫函式OsMemReAllocSmaller()進行分割,分割完畢返回(VOID *)ptr即可。如果重新申請更大的記憶體,則執行⑵處程式碼獲取下一個節點,然後執行⑶處理下一個節點可用且兩個節點大小之和大於等於重新申請記憶體的大小allocSize。執行⑷處的函式,合併節點重新分配記憶體。

如果連續的節點的大小不滿足重新申請記憶體的大小,則執行⑸處函式重新申請記憶體。申請成功後,執行⑹把之前記憶體的資料複製到新申請的記憶體區域,複製失敗的話,則把新申請的記憶體釋放掉,並返回NULL,退出函式。如果複製成功,繼續執行⑺釋放掉之前的節點。

STATIC INLINE VOID *OsMemRealloc(struct OsMemPoolHead *pool, const VOID *ptr,
                struct OsMemNodeHead *node, UINT32 size, UINT32 intSave)
{
    struct OsMemNodeHead *nextNode = NULL;
    UINT32 allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);
    UINT32 nodeSize = OS_MEM_NODE_GET_SIZE(node->sizeAndFlag);
    VOID *tmpPtr = NULL;

⑴  if (nodeSize >= allocSize) {
        OsMemReAllocSmaller(pool, allocSize, node, nodeSize);
        return (VOID *)ptr;
    }

⑵  nextNode = OS_MEM_NEXT_NODE(node);
⑶  if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->sizeAndFlag) &&
        ((nextNode->sizeAndFlag + nodeSize) >= allocSize)) {
⑷      OsMemMergeNodeForReAllocBigger(pool, allocSize, node, nodeSize, nextNode);
        return (VOID *)ptr;
    }

⑸  tmpPtr = OsMemAlloc(pool, size, intSave);
    if (tmpPtr != NULL) {
⑹      if (memcpy_s(tmpPtr, size, ptr, (nodeSize - OS_MEM_NODE_HEAD_SIZE)) != EOK) {
            MEM_UNLOCK(pool, intSave);
            (VOID)LOS_MemFree((VOID *)pool, (VOID *)tmpPtr);
            MEM_LOCK(pool, intSave);
            return NULL;
        }
⑺      (VOID)OsMemFree(pool, node);
    }
    return tmpPtr;
}

小結

本文帶領大家一起剖析了鴻蒙輕核心的動態記憶體模組的原始碼,包含動態記憶體的結構體、動態記憶體池初始化、動態記憶體申請、釋放等。感謝閱讀,如有任何問題、建議,都可以留言給我們: https://gitee.com/openharmony/kernel_liteos_m/issues 。為了更容易找到鴻蒙輕核心程式碼倉,建議訪問 https://gitee.com/openharmony/kernel_liteos_m ,關注Watch、點贊Star、並Fork到自己賬戶下,謝謝。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章