摘要:本文帶領你們一塊兒剖析了鴻蒙輕內核的動態內存模塊的源代碼,包含動態內存的結構體、動態內存池初始化、動態內存申請、釋放等。
本文分享自華爲雲社區《鴻蒙輕內核M核源碼分析系列九 動態內存Dynamic Memory 第一部分》,原文做者:zhushy。node
內存管理模塊管理系統的內存資源,它是操做系統的核心模塊之一,主要包括內存的初始化、分配以及釋放。git
在系統運行過程當中,內存管理模塊經過對內存的申請/釋放來管理用戶和OS對內存的使用,使內存的利用率和使用效率達到最優,同時最大限度地解決系統的內存碎片問題。算法
鴻蒙輕內核的內存管理分爲靜態內存管理和動態內存管理,提供內存初始化、分配、釋放等功能。數組
- 動態內存:在動態內存池中分配用戶指定大小的內存塊。
- 優勢:按需分配。
- 缺點:內存池中可能出現碎片。
- 靜態內存:在靜態內存池中分配用戶初始化時預設(固定)大小的內存塊。
- 優勢:分配和釋放效率高,靜態內存池中無碎片。
- 缺點:只能申請到初始化預設大小的內存塊,不能按需申請。
上一系列分析了靜態內存,咱們開始分析動態內存。動態內存管理主要用於用戶須要使用大小不等的內存塊的場景。當用戶須要使用內存時,能夠經過操做系統的動態內存申請函數索取指定大小的內存塊,一旦使用完畢,經過動態內存釋放函數歸還所佔用內存,使之能夠重複使用。ide
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 動態內存結構體定義
動態內存的結構體有動態內存池信息結構體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.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
到本身帳戶下,謝謝。