摘要:本文帶領大家一起剖析了鴻蒙輕內核的動態內存模塊的源代碼,包含動態內存的結構體、動態內存池初始化、動態內存申請、釋放等。
本文分享自華為雲社區《鴻蒙輕內核M核源碼分析系列九 動態內存Dynamic Memory 第一部分》,原文作者:zhushy。
內存管理模塊管理系統的內存資源,它是操作系統的核心模塊之一,主要包括內存的初始化、分配以及釋放。
在系統運行過程中,內存管理模塊通過對內存的申請/釋放來管理用戶和OS對內存的使用,使內存的利用率和使用效率達到最優,同時最大限度地解決系統的內存碎片問題。
鴻蒙輕內核的內存管理分為靜態內存管理和動態內存管理,提供內存初始化、分配、釋放等功能。
- 動態內存:在動態內存池中分配用戶指定大小的內存塊。
- 優點:按需分配。
- 缺點:內存池中可能出現碎片。
- 靜態內存:在靜態內存池中分配用戶初始化時預設(固定)大小的內存塊。
- 優點:分配和釋放效率高,靜態內存池中無碎片。
- 缺點:只能申請到初始化預設大小的內存塊,不能按需申請。
上一系列分析了靜態內存,我們開始分析動態內存。動態內存管理主要用於用戶需要使用大小不等的內存塊的場景。當用戶需要使用內存時,可以通過操作系統的動態內存申請函數索取指定大小的內存塊,一旦使用完畢,通過動態內存釋放函數歸還所占用內存,使之可以重復使用。
OpenHarmony LiteOS-M動態內存在TLSF算法的基礎上,對區間的划分進行了優化,獲得更優的性能,降低了碎片率。動態內存核心算法框圖如下:
根據空閑內存塊的大小,使用多個空閑鏈表來管理。根據內存空閑塊大小分為兩個部分:[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字節的內存時,根據位圖標記獲取存在滿足申請大小的內存塊的空閑鏈表,從空閑鏈表上獲取空閑內存節點。如果分配的節點大於需要申請的內存大小,進行分割節點操作,剩余的節點重新掛載到相應的空閑鏈表上。如果對應的空閑鏈表為空,則向更大的內存區間去查詢是否有滿足條件的空閑鏈表,實際計算時,會一次性查找到滿足申請大小的空閑鏈表。
動態內存管理結構如下圖所示:
- 內存池池頭部分
內存池池頭部分包含內存池信息和位圖標記數組和空閑鏈表數組。內存池信息包含內存池起始地址及堆區域總大小,內存池屬性。位圖標記數組有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(); #endif ⑴ if ((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); #endif ⑸ return 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(); #endif ⑴ if ((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(); #endif ⑴ if ((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
到自己賬戶下,謝謝。