摘要:本文為大家剖析LiteOS動態內存模塊bestfit算法的源代碼,包含動態內存的結構體、動態內存池初始化、動態內存申請、釋放等。
內存管理模塊管理系統的內存資源,它是操作系統的核心模塊之一,主要包括內存的初始化、分配以及釋放。
在系統運行過程中,內存管理模塊通過對內存的申請/釋放來管理用戶和OS對內存的使用,使內存的利用率和使用效率達到最優,同時最大限度地解決系統的內存碎片問題。
Huawei LiteOS的內存管理分為靜態內存管理和動態內存管理,提供內存初始化、分配、釋放等功能。
- 動態內存:在動態內存池中分配用戶指定大小的內存塊。
- 優點:按需分配。
- 缺點:內存池中可能出現碎片。
- 靜態內存:在靜態內存池中分配用戶初始化時預設(固定)大小的內存塊。
- 優點:分配和釋放效率高,靜態內存池中無碎片。
- 缺點:只能申請到初始化預設大小的內存塊,不能按需申請。
上一系列分析了靜態內存,我們開始分析動態內存。動態內存管理主要用於用戶需要使用大小不等的內存塊的場景。當用戶需要使用內存時,可以通過操作系統的動態內存申請函數索取指定大小的內存塊,一旦使用完畢,通過動態內存釋放函數歸還所占用內存,使之可以重復使用。
LiteOS動態內存支持bestfit(也稱為dlink)和bestfit_little兩種內存管理算法。本文主要分析LiteOS動態內存的bestfit算法,后續系列會繼續分析動態內存的bestfit_little的算法。
本文通過分析LiteOS動態內存模塊的源碼,幫助讀者掌握動態內存的使用。LiteOS動態內存模塊的源代碼,均可以在LiteOS開源站點https://gitee.com/LiteOS/LiteOS 獲取。動態內存bestfit算法的源代碼、開發文檔,示例程序代碼如下:
- LiteOS內核動態內存源代碼
包括動態內存的bestfit算法私有頭文件kernel\base\mem\bestfit\los_memory_internal.h、動態內存私有頭文件kernel\base\include\los_memory_pri.h、內存頭文件kernel\include\los_memory.h、多鏈表頭文件kernel\base\include\los_multipledlinkhead_pri.h、C源代碼文件kernel\base\mem\bestfit\los_memory.c、C源代碼文件kernel\base\mem\bestfit\los_multipledlinkhead.c。
- 開發指南文檔–內存
在線文檔https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E5%86%85%E5%AD%98。
接下來,我們看下動態內存的結構體,動態內存初始化,動態內存常用操作的源代碼。
1、動態內存結構體定義和常用宏定義
1.1 動態內存結構體定義
動態內存bestfit算法的結構體有動態內存池信息結構體LosMemPoolInfo,多雙向鏈表表頭結構體LosMultipleDlinkHead、動態內存鏈表節點結構體LosMemDynNode,內存鏈表控制節點結構體LosMemCtlNode。
對動態內存使用如下示意圖進行說明,對一塊動態內存區域,第一部分是內存池信息結構體LosMemPoolInfo,接着是第二部分多雙向鏈表表頭結構體LosMultipleDlinkHead,第三部分是動態內存鏈表節點結構體LosMemDynNode,內存鏈表控制節點結構體LosMemCtlNode。使用動態內存的bestfit算法初始化后,第三部分包含2個內存塊,第一個內存塊包含內存鏈表控制節點結構體LosMemCtlNode和內存塊數據區,尾節點只包含內存鏈表控制節點結構體LosMemCtlNode,沒有數據區。控制節點結構體LosMemCtlNode持有上一個內存塊的指針。有數據區的內存塊會掛載在第二部分的鏈表上。當申請內存時,根據需要內存的大小,從第二部分的鏈表獲取合適的內存塊,如果內存塊超出需求,會進行切割,剩余的部分和后續的空閑節點合並,重新掛載到第二部分的鏈表上。

1.1.1 動態內存池信息結構體LosMemPoolInfo
在文件kernel\base\include\los_memory_pri.h中,定義了內存池信息結構體LosMemPoolInfo。這是動態內存池的第一部分,維護內存池的開始地址和大小信息。動態內存bestfit算法和bestfit_little算法中都定義了該結構體,結構體名稱一樣,成員有差異,我們先看看bestfit算法的結構體,源代碼如下。兩個主要的成員是內存池開始地址.pool和內存池大小.poolSize。其他結構體需要開啟相應的宏才生效,暫不討論這些宏相關的特性。
typedef struct { VOID *pool; /* 內存池的內存開始地址 */ UINT32 poolSize; /* 內存池大小 */ #ifdef LOSCFG_MEM_TASK_STAT Memstat stat; #endif #ifdef LOSCFG_MEM_MUL_POOL VOID *nextPool; #endif #ifdef LOSCFG_KERNEL_MEM_SLAB_EXTENTION struct LosSlabControlHeader slabCtrlHdr; #endif } LosMemPoolInfo;
1.1.2 多雙向鏈表表頭結構體LosMultipleDlinkHead
在文件kernel\base\include\los_multipledlinkhead_pri.h中,定義了內存池多雙向鏈表表頭結構體LosMultipleDlinkHead。這是動態內存池的第二部分,結構體本身是一個數組,每個元素是一個雙向鏈表,所有free節點的控制頭都會被分類掛在這個數組的雙向鏈表中。
假設內存允許的最小節點為2^min字節,則數組的第一個雙向鏈表存儲的是所有size為2^min<size< 2^(min+1)的free節點,第二個雙向鏈表存儲的是所有size為2^(min+1)<size< 2^(min+2)的free節點,依次類推第n個雙向鏈表存儲的是所有size為2^(min+n-1)<size< 2^(min+n)的free節點。每次申請內存的時候,會從這個數組檢索最合適大小的free節點以分配內存。每次釋放內存時,會將該內存作為free節點存儲至這個數組以便下次再使用。
結構體源代碼如下,非常簡單,是一個長度為OS_MULTI_DLNK_NUM的雙向鏈表數組。
typedef struct { LOS_DL_LIST listHead[OS_MULTI_DLNK_NUM]; } LosMultipleDlinkHead;
我們再看看和結構體LosMultipleDlinkHead相關的宏定義,OS_MIN_MULTI_DLNK_LOG2和OS_MAX_MULTI_DLNK_LOG2指定了雙向鏈表中存儲的內存節點的大小訪問,第一個存儲大小在[2^4,2^5)的空閑內存節點,依次類推,第26個即OS_MULTI_DLNK_NUM存儲大小在[2^29,2^30)的空閑內存節點。多鏈表表頭結構體占用的內存大小為OS_DLNK_HEAD_SIZE。
#define OS_MAX_MULTI_DLNK_LOG2 29 #define OS_MIN_MULTI_DLNK_LOG2 4 #define OS_MULTI_DLNK_NUM ((OS_MAX_MULTI_DLNK_LOG2 - OS_MIN_MULTI_DLNK_LOG2) + 1) #define OS_DLNK_HEAD_SIZE OS_MULTI_DLNK_HEAD_SIZE #define OS_MULTI_DLNK_HEAD_SIZE sizeof(LosMultipleDlinkHead)
1.1.3 動態內存鏈表節點結構體LosMemDynNode和鏈表控制節點結構體LosMemCtlNode
在文件kernel\base\mem\bestfit\los_memory_internal.h中定義2個結構體,動態內存鏈表節點結構體LosMemDynNode和鏈表控制節點結構體LosMemCtlNode。這是動態內存池的第三部分,占用內存池極大部分的空間,是用於存放各節點的實際區域。設計2個結構體的原因為滿足備份內存鏈表節點的需要。可以看出開啟備份鏈表節點的宏LOSCFG_MEM_HEAD_BACKUP時,LosMemDynNode結構體包含2個LosMemCtlNode,一個是.backupNode,另外一個是.selfNode。2個結構體源碼如下,重要的成員的解釋見注釋部分。
/* 內存鏈表控制節點結構體 */ typedef struct { union { LOS_DL_LIST freeNodeInfo; /* 空閑內存鏈表節點 */ struct { UINT32 magic; /* 魔術字 */ UINT32 taskId : 16; /* 使用內存的任務Id */ #ifdef LOSCFG_MEM_MUL_MODULE UINT32 moduleId : 16; #endif }; }; struct tagLosMemDynNode *preNode; /* 指針,指針前一個內存節點 */ #ifdef LOSCFG_MEM_HEAD_BACKUP UINT32 gapSize; UINTPTR checksum; /* magic = xor checksum */ #endif #ifdef LOSCFG_MEM_RECORDINFO UINT32 originSize; #ifdef LOSCFG_AARCH64 UINT32 reserve1; /* 64-bit alignment */ #endif #endif #ifdef LOSCFG_MEM_LEAKCHECK UINTPTR linkReg[LOS_RECORD_LR_CNT]; #endif #ifdef LOSCFG_AARCH64 UINT32 reserve2; /* 64-bit alignment */ #endif UINT32 sizeAndFlag; /* 大小和標志,高2位用作標記,其余位表示大小 */ } LosMemCtlNode; /* 內存鏈表節點結構體 */ typedef struct tagLosMemDynNode { #ifdef LOSCFG_MEM_HEAD_BACKUP LosMemCtlNode backupNode; #endif LosMemCtlNode selfNode; } LosMemDynNode;
1.2 動態內存常用宏定義
動態內存頭文件kernel\base\mem\bestfit\los_memory_internal.h中還提供了一些重要的宏定義,這些宏非常重要,在分析源代碼前需要熟悉下這些宏的定義。
⑴處的OS_MEM_ALIGN(p, alignSize)用於對齊內存地址,⑵處OS_MEM_NODE_HEAD_SIZE表示一個內存鏈表節點的大小,OS_MEM_MIN_POOL_SIZE表示一個動態內存池的最小大小,包含一個內存池信息結構體大小,1個多鏈表表頭結構體大小,和2個鏈表節點大小。⑶處IS_POW_TWO(value)判斷value是否是2的冪。⑷處定義內存池地址對齊值,內存節點對齊值。
⑸處定義是否使用、是否對齊2個標記位,分別是高31位,和高30位。然后分別定義3個宏函數,用於獲取是否已使用/對齊,設置標記為使用/對齊,獲取去除標記后的使用大小。
⑹處OS_MEM_HEAD_ADDR(pool)表示動態內存池第二部分多鏈表表頭結構體的開始地址。宏函數OS_MEM_HEAD(pool, size)用於計算大小為size的內存塊對應的多鏈表表頭的地址,其實就是把內存池第三部分的內存塊的大小映射到第二部分的鏈表位置上。其中調用的函數OsDLnkMultiHead()后文我們再分析。
⑺處定義內存節點操作相關的宏。OS_MEM_NEXT_NODE(node)獲取內存節點的下一個內存節點,OS_MEM_FIRST_NODE(pool)獲取內存池中第一個內存節點,OS_MEM_END_NODE(pool, size)獲取內存池中最后一個內存節點。
⑻處定義2個宏,判斷一個內存地址是否處於指定的區間,兩者區別是是否開閉區間。⑼處的2個宏對邊界內存節點設置魔術字和魔術字校驗。
⑴ #define OS_MEM_ALIGN(p, alignSize) (((UINTPTR)(p) + (alignSize) - 1) & ~((UINTPTR)((alignSize) - 1))) ⑵ #define OS_MEM_NODE_HEAD_SIZE sizeof(LosMemDynNode) #define OS_MEM_MIN_POOL_SIZE (OS_DLNK_HEAD_SIZE + (2 * OS_MEM_NODE_HEAD_SIZE) + sizeof(LosMemPoolInfo)) ⑶ #define IS_POW_TWO(value) ((((UINTPTR)(value)) & ((UINTPTR)(value) - 1)) == 0) ⑷ #define POOL_ADDR_ALIGNSIZE 64 #ifdef LOSCFG_AARCH64 #define OS_MEM_ALIGN_SIZE 8 #else #define OS_MEM_ALIGN_SIZE 4 #endif ⑸ #define OS_MEM_NODE_USED_FLAG 0x80000000U #define OS_MEM_NODE_ALIGNED_FLAG 0x40000000U #define OS_MEM_NODE_ALIGNED_AND_USED_FLAG (OS_MEM_NODE_USED_FLAG | OS_MEM_NODE_ALIGNED_FLAG) #define OS_MEM_NODE_GET_ALIGNED_FLAG(sizeAndFlag) \ ((sizeAndFlag) & OS_MEM_NODE_ALIGNED_FLAG) #define OS_MEM_NODE_SET_ALIGNED_FLAG(sizeAndFlag) \ ((sizeAndFlag) = ((sizeAndFlag) | OS_MEM_NODE_ALIGNED_FLAG)) #define OS_MEM_NODE_GET_ALIGNED_GAPSIZE(sizeAndFlag) \ ((sizeAndFlag) & ~OS_MEM_NODE_ALIGNED_FLAG) #define OS_MEM_NODE_GET_USED_FLAG(sizeAndFlag) \ ((sizeAndFlag) & OS_MEM_NODE_USED_FLAG) #define OS_MEM_NODE_SET_USED_FLAG(sizeAndFlag) \ ((sizeAndFlag) = ((sizeAndFlag) | OS_MEM_NODE_USED_FLAG)) #define OS_MEM_NODE_GET_SIZE(sizeAndFlag) \ ((sizeAndFlag) & ~OS_MEM_NODE_ALIGNED_AND_USED_FLAG) ⑹ #define OS_MEM_HEAD(pool, size) \ OsDLnkMultiHead(OS_MEM_HEAD_ADDR(pool), size) #define OS_MEM_HEAD_ADDR(pool) \ ((VOID *)((UINTPTR)(pool) + sizeof(LosMemPoolInfo))) ⑺ #define OS_MEM_NEXT_NODE(node) \ ((LosMemDynNode *)(VOID *)((UINT8 *)(node) + OS_MEM_NODE_GET_SIZE((node)->selfNode.sizeAndFlag))) #define OS_MEM_FIRST_NODE(pool) \ ((LosMemDynNode *)(VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE)) #define OS_MEM_END_NODE(pool, size) \ ((LosMemDynNode *)(VOID *)(((UINT8 *)(pool) + (size)) - OS_MEM_NODE_HEAD_SIZE)) ⑻ #define OS_MEM_MIDDLE_ADDR_OPEN_END(startAddr, middleAddr, endAddr) \ (((UINT8 *)(startAddr) <= (UINT8 *)(middleAddr)) && ((UINT8 *)(middleAddr) < (UINT8 *)(endAddr))) #define OS_MEM_MIDDLE_ADDR(startAddr, middleAddr, endAddr) \ (((UINT8 *)(startAddr) <= (UINT8 *)(middleAddr)) && ((UINT8 *)(middleAddr) <= (UINT8 *)(endAddr))) ⑼ #define OS_MEM_SET_MAGIC(value) \ (value) = (UINT32)((UINTPTR)&(value) ^ (UINTPTR)(-1)) #define OS_MEM_MAGIC_VALID(value) \ (((UINTPTR)(value) ^ (UINTPTR)&(value)) == (UINTPTR)(-1))
我們看下宏中調用的函數OsDLnkMultiHead(),函數定義在文件kernel\base\mem\bestfit\los_multipledlinkhead.c中。該函數需要2個參數,VOID *headAddr為第二部分的多鏈表數組的起始地址,UINT32 size為內存塊的大小。該函數把內存池第三部分的內存塊的大小映射到第二部分的鏈表位置上,我們分析下代碼。
⑴處的函數OsLog2()名稱中的Log是對數英文logarithm的縮寫,函數用於計算以2為底的對數的整數部分,輸入參數是內存池第三部分的內存塊的大小size,輸出是第二部分多鏈表數組的數組索引,見代碼片段⑵。⑶處如果索引大於OS_MAX_MULTI_DLNK_LOG2,無法分配這么大的內存塊,返回NULL。⑷處如果索引小於等於OS_MIN_MULTI_DLNK_LOG2,則使用最小值作為索引。⑸處返回多鏈表表頭中的鏈表頭節點。
STATIC INLINE UINT32 OsLog2(UINT32 size) { ⑴ return (size > 0) ? (UINT32)LOS_HighBitGet(size) : 0; } LITE_OS_SEC_TEXT_MINOR LOS_DL_LIST *OsDLnkMultiHead(VOID *headAddr, UINT32 size) { LosMultipleDlinkHead *dlinkHead = (LosMultipleDlinkHead *)headAddr; ⑵ UINT32 index = OsLog2(size); if (index > OS_MAX_MULTI_DLNK_LOG2) { ⑶ return NULL; } else if (index <= OS_MIN_MULTI_DLNK_LOG2) { ⑷ index = OS_MIN_MULTI_DLNK_LOG2; } ⑸ return dlinkHead->listHead + (index - OS_MIN_MULTI_DLNK_LOG2); }
2、動態內存常用操作
Huawei LiteOS系統中的動態內存管理模塊為用戶提供初始化和刪除內存池、申請、釋放動態內存等操作,我們來分析下接口的源代碼。在分析下內存操作接口之前,我們先看下一下常用的內部接口。
2.1 動態內存內部接口
2.1.1 清除內存節點內容
函數VOID OsMemClearNode(LosMemDynNode *node)用於清除給定的內存節點內容,設置內存數據內容為0。代碼比較簡單,直接調用函數memset_s()完成操作。
STATIC INLINE VOID OsMemClearNode(LosMemDynNode *node) { (VOID)memset_s((VOID *)node, sizeof(LosMemDynNode), 0, sizeof(LosMemDynNode)); }
2.1.2 合並內存節點
函數VOID OsMemMergeNode(LosMemDynNode *node)用於合並給定節點LosMemDynNode *node和它前一個空閑節點,然后清除給定節點的內容。⑴處把前一個節點的大小加上要合入節點的大小。⑵處獲取給定節點的下一個節點,然后把它的前一個節點指向給定節點的前一個節點。⑶處清除給定節點的內容,完成節點的合並。
STATIC INLINE VOID OsMemMergeNode(LosMemDynNode *node) { LosMemDynNode *nextNode = NULL; ⑴ node->selfNode.preNode->selfNode.sizeAndFlag += node->selfNode.sizeAndFlag; ⑵ nextNode = (LosMemDynNode *)((UINTPTR)node + node->selfNode.sizeAndFlag); nextNode->selfNode.preNode = node->selfNode.preNode; #ifdef LOSCFG_MEM_HEAD_BACKUP OsMemNodeSave(node->selfNode.preNode); OsMemNodeSave(nextNode); #endif ⑶ OsMemClearNode(node); }
2.1.3 分割內存節點
函數VOID OsMemSplitNode(VOID *pool, LosMemDynNode *allocNode, UINT32 allocSize)用於分割內存節點,需要三個參數。VOID *pool是內存池起始地址,LosMemDynNode *allocNode表示從該內存節點分配出需要的內存,UINT32 allocSize是需要分配的內存大小。分割之后剩余的部分,如果下一個節點是空閑節點,則合並一起。分割剩余的節點會掛載到內存第二部分的多鏈表上。
⑴處獲取動態內存池的第一個內存控制節點,⑵處表示newFreeNode是分配之后剩余的空閑內存節點,設置它的上一個節點為分配的節點,並設置剩余內存大小。⑶處獲取下一個節點,下一個節點的前一個節點設置為新的空閑節點newFreeNode。⑷處判斷下一個節點是否被使用,如果沒有使用,則把下一個節點從鏈表中刪除,然后和空閑節點newFreeNode合並。⑸處根據空閑節點newFreeNode的大小獲取對應的鏈表頭節點,然后執行⑹把空閑內存節點掛載到鏈表上。
STATIC INLINE VOID OsMemSplitNode(VOID *pool, LosMemDynNode *allocNode, UINT32 allocSize) { LosMemDynNode *newFreeNode = NULL; LosMemDynNode *nextNode = NULL; LOS_DL_LIST *listNodeHead = NULL; ⑴ const VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE); ⑵ newFreeNode = (LosMemDynNode *)(VOID *)((UINT8 *)allocNode + allocSize); newFreeNode->selfNode.preNode = allocNode; newFreeNode->selfNode.sizeAndFlag = allocNode->selfNode.sizeAndFlag - allocSize; allocNode->selfNode.sizeAndFlag = allocSize; ⑶ nextNode = OS_MEM_NEXT_NODE(newFreeNode); nextNode->selfNode.preNode = newFreeNode; ⑷ if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag)) { OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode); OsMemMergeNode(nextNode); } #ifdef LOSCFG_MEM_HEAD_BACKUP else { OsMemNodeSave(nextNode); } #endif ⑸ listNodeHead = OS_MEM_HEAD(pool, newFreeNode->selfNode.sizeAndFlag); OS_CHECK_NULL_RETURN(listNodeHead); ⑹ OsMemListAdd(listNodeHead, &newFreeNode->selfNode.freeNodeInfo, firstNode); #ifdef LOSCFG_MEM_HEAD_BACKUP OsMemNodeSave(newFreeNode); #endif }
2.1.4 重新申請內存
OsMemReAllocSmaller()函數用於從一個大的內存塊里重新申請一個較小的內存,他需要的4個參數分別是:LosMemPoolInfo *pool是內存池起始地址,UINT32 allocSize是重新申請的內存的大小,LosMemDynNode *node是當前需要重新分配內存的內存節點,UINT32 nodeSize是當前節點的大小。⑴設置內存節點selfNode.sizeAndFlag為去除標記后的實際大小,⑵按需分割節點,⑶分割后的節點設置已使用標記,完成完成申請內存。
STATIC INLINE VOID OsMemReAllocSmaller(LosMemPoolInfo *pool, UINT32 allocSize, LosMemDynNode *node, UINT32 nodeSize) { if ((allocSize + OS_MEM_NODE_HEAD_SIZE + OS_MEM_ALIGN_SIZE) <= nodeSize) { ⑴ node->selfNode.sizeAndFlag = nodeSize; ⑵ OsMemSplitNode(pool, node, allocSize); ⑶ OS_MEM_NODE_SET_USED_FLAG(node->selfNode.sizeAndFlag); #ifdef LOSCFG_MEM_HEAD_BACKUP OsMemNodeSave(node); #endif OS_MEM_REDUCE_USED(&pool->stat, nodeSize - allocSize, OS_MEM_TASKID_GET(node)); } #ifdef LOSCFG_MEM_LEAKCHECK OsMemLinkRegisterRecord(node); #endif }
2.1.5 合並節點重新申請內存
最后,再來看下函數函數OsMemMergeNodeForReAllocBigger(),用於合並內存節點,重新分配更大的內存空間。它需要5個參數,LosMemPoolInfo *pool是內存池起始地址,UINT32 allocSize是重新申請的內存的大小,LosMemDynNode *node是當前需要重新分配內存的內存節點,UINT32 nodeSize是當前節點的大小,LosMemDynNode *nextNode是下一個內存節點。⑴處獲取內存池中第一個內存節點,⑵設置內存節點selfNode.sizeAndFlag為去除標記后的實際大小,⑶把下一個節點從鏈表上刪除,然后合並節點。⑷處如果合並后的節點大小超過需要重新分配的大小,則分割節點。
STATIC INLINE VOID OsMemMergeNodeForReAllocBigger(LosMemPoolInfo *pool, UINT32 allocSize, LosMemDynNode *node, UINT32 nodeSize, LosMemDynNode *nextNode) { ⑴ const VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE); ⑵ node->selfNode.sizeAndFlag = nodeSize; ⑶ OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode); OsMemMergeNode(nextNode); if ((allocSize + OS_MEM_NODE_HEAD_SIZE + OS_MEM_ALIGN_SIZE) <= node->selfNode.sizeAndFlag) { ⑷ OsMemSplitNode(pool, node, allocSize); } OS_MEM_ADD_USED(&pool->stat, node->selfNode.sizeAndFlag - nodeSize, OS_MEM_TASKID_GET(node)); OS_MEM_NODE_SET_USED_FLAG(node->selfNode.sizeAndFlag); #ifdef LOSCFG_MEM_HEAD_BACKUP OsMemNodeSave(node); #endif #ifdef LOSCFG_MEM_LEAKCHECK OsMemLinkRegisterRecord(node); #endif }
2.2 初始化動態內存池
我們分析下初始化動態內存池函數UINT32 LOS_MemInit(VOID *pool, UINT32 size)的代碼。我們先看看函數參數,VOID *pool是動態內存池的起始地址,UINT32 size是初始化的動態內存池的總大小,size需要小於等於*pool開始的內存區域的大小,否則會影響后面的內存區域,還需要大於動態內存池的最小值OS_MEM_MIN_POOL_SIZE。[pool, pool + size]不能和其他內存池沖突。
我們看下代碼,⑴處對傳入參數進行校驗,傳入參數需要內存對齊。⑵處如果開啟多內存池的宏LOSCFG_MEM_MUL_POOL才會執行。⑶處調用函數OsMemInit()進行內存池初始化,這是初始化的內存的核心函數。⑷處開啟宏LOSCFG_KERNEL_MEM_SLAB_EXTENTION支持時,才會執行。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_MemInit(VOID *pool, UINT32 size) { UINT32 intSave; ⑴ if ((pool == NULL) || (size < OS_MEM_MIN_POOL_SIZE)) { return LOS_NOK; } if (!IS_ALIGNED(size, OS_MEM_ALIGN_SIZE) || !IS_ALIGNED(pool, OS_MEM_ALIGN_SIZE)) { PRINT_WARN("pool [%p, %p) size 0x%x should be aligned with OS_MEM_ALIGN_SIZE\n", pool, (UINTPTR)pool + size, size); size = OS_MEM_ALIGN(size, OS_MEM_ALIGN_SIZE) - OS_MEM_ALIGN_SIZE; } MEM_LOCK(intSave); ⑵ if (OsMemMulPoolInit(pool, size)) { MEM_UNLOCK(intSave); return LOS_NOK; } ⑶ if (OsMemInit(pool, size) != LOS_OK) { (VOID)OsMemMulPoolDeinit(pool); MEM_UNLOCK(intSave); return LOS_NOK; } ⑷ OsSlabMemInit(pool, size); MEM_UNLOCK(intSave); LOS_TRACE(MEM_INFO_REQ, pool); return LOS_OK; }
我們繼續看下函數OsMemInit()。⑴處設置動態內存池信息結構體LosMemPoolInfo的起始地址和大小。⑵處初始化第二部分的多雙向鏈表表頭結構體LosMultipleDlinkHead。⑶處獲取內存池的第一個內存控制節點,然后設置它的大小為(poolSize - (UINT32)((UINTPTR)newNode - (UINTPTR)pool) - OS_MEM_NODE_HEAD_SIZE),該節點大小等於第三部分減去一個內存控制節點的大小。再設置該內存節點的上一個節點為內存池的最后一個節點OS_MEM_END_NODE(pool, poolSize)。
⑷處根據第一個內存節點的大小獲取多鏈表表頭節點listNodeHead,然后把內存節點插入到雙向鏈表中。⑸處獲取內存池的尾節點,清除內容然后設置其大小和上一個節點,並設置已使用標記。⑹處對未節點設置魔術字,指定使用該內存塊的任務Id。如果開啟調測宏LOSCFG_MEM_TASK_STAT、LOSCFG_MEM_HEAD_BACKUP,還會有些其他操作,自行閱讀即可。
STATIC UINT32 OsMemInit(VOID *pool, UINT32 size) { LosMemPoolInfo *poolInfo = (LosMemPoolInfo *)pool; LosMemDynNode *newNode = NULL; LosMemDynNode *endNode = NULL; LOS_DL_LIST *listNodeHead = NULL; UINT32 poolSize = OsLmsMemInit(pool, size); if (poolSize == 0) { poolSize = size; } ⑴ poolInfo->pool = pool; poolInfo->poolSize = poolSize; ⑵ OsDLnkInitMultiHead(OS_MEM_HEAD_ADDR(pool)); ⑶ newNode = OS_MEM_FIRST_NODE(pool); newNode->selfNode.sizeAndFlag = (poolSize - (UINT32)((UINTPTR)newNode - (UINTPTR)pool) - OS_MEM_NODE_HEAD_SIZE); newNode->selfNode.preNode = (LosMemDynNode *)OS_MEM_END_NODE(pool, poolSize); ⑷ listNodeHead = OS_MEM_HEAD(pool, newNode->selfNode.sizeAndFlag); if (listNodeHead == NULL) { return LOS_NOK; } LOS_ListTailInsert(listNodeHead, &(newNode->selfNode.freeNodeInfo)); ⑸ endNode = (LosMemDynNode *)OS_MEM_END_NODE(pool, poolSize); (VOID)memset_s(endNode, sizeof(*endNode), 0, sizeof(*endNode)); endNode->selfNode.preNode = newNode; endNode->selfNode.sizeAndFlag = OS_MEM_NODE_HEAD_SIZE; OS_MEM_NODE_SET_USED_FLAG(endNode->selfNode.sizeAndFlag); ⑹ OsMemSetMagicNumAndTaskID(endNode); #ifdef LOSCFG_MEM_TASK_STAT UINT32 statSize = sizeof(poolInfo->stat); (VOID)memset_s(&poolInfo->stat, statSize, 0, statSize); poolInfo->stat.memTotalUsed = sizeof(LosMemPoolInfo) + OS_MULTI_DLNK_HEAD_SIZE + OS_MEM_NODE_GET_SIZE(endNode->selfNode.sizeAndFlag); poolInfo->stat.memTotalPeak = poolInfo->stat.memTotalUsed; #endif #ifdef LOSCFG_MEM_HEAD_BACKUP OsMemNodeSave(newNode); OsMemNodeSave(endNode); #endif return LOS_OK; }
2.3 申請動態內存
初始化動態內存池后,我們可以使用函數VOID *LOS_MemAlloc(VOID *pool, UINT32 size)來申請動態內存,下面分析下源碼。
⑴處對參數進行校驗,內存池地址不能為空,申請的內存大小不能為0。⑵處判斷申請的內存大小是否已標記為使用或對齊。把下一個可用節點賦值給nodeTmp。⑶處如果支持SLAB,則先嘗試從SLAB中獲取內存,否則執行⑷調用函數OsMemAllocWithCheck(pool, size)申請內存塊。
LITE_OS_SEC_TEXT VOID *LOS_MemAlloc(VOID *pool, UINT32 size) { VOID *ptr = NULL; UINT32 intSave; ⑴ if ((pool == NULL) || (size == 0)) { return NULL; } if (g_MALLOC_HOOK != NULL) { g_MALLOC_HOOK(); } MEM_LOCK(intSave); do { ⑵ if (OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size)) { break; } ⑶ ptr = OsSlabMemAlloc(pool, size); if (ptr == NULL) { ⑷ ptr = OsMemAllocWithCheck(pool, size); } } while (0); #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordMalloc(ptr, size); #endif OsLmsSetAfterMalloc(ptr); MEM_UNLOCK(intSave); LOS_TRACE(MEM_ALLOC, pool, (UINTPTR)ptr, size); return ptr; }
我們繼續分析函數OsMemAllocWithCheck(pool, size)。⑴處獲取內存池中第一個內存節點,⑵計算出對齊后的內存大小,然后調用函數OsMemFindSuitableFreeBlock()獲取適合的內存塊,如果找不到適合的內存塊,函數返回NULL。⑶處如果找到的內存塊大於需要的內存大小,則執行分割操作。⑷處把已分配的內存節點從鏈表中刪除,然后設置魔術字和使用該內存塊的任務Id,然后標記該內存塊已使用。⑸處如果開啟宏LOSCFG_MEM_TASK_STAT,還需要做些記錄操作,自行分析即可。⑹處返回內存塊的數據區的地址,這個是通過內存控制節點+1定位到數據區內存地址實現的。申請內存完成,調用申請內存的函數中可以使用申請的內存了。
STATIC VOID *OsMemAllocWithCheck(LosMemPoolInfo *pool, UINT32 size) { LosMemDynNode *allocNode = NULL; UINT32 allocSize; #ifdef LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK LosMemDynNode *tmpNode = NULL; LosMemDynNode *preNode = NULL; #endif ⑴ const VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE); #ifdef LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK if (OsMemIntegrityCheck(pool, &tmpNode, &preNode)) { OsMemIntegrityCheckError(tmpNode, preNode); return NULL; } #endif ⑵ allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE); allocNode = OsMemFindSuitableFreeBlock(pool, allocSize); if (allocNode == NULL) { OsMemInfoAlert(pool, allocSize); return NULL; } ⑶ if ((allocSize + OS_MEM_NODE_HEAD_SIZE + OS_MEM_ALIGN_SIZE) <= allocNode->selfNode.sizeAndFlag) { OsMemSplitNode(pool, allocNode, allocSize); } ⑷ OsMemListDelete(&allocNode->selfNode.freeNodeInfo, firstNode); OsMemSetMagicNumAndTaskID(allocNode); OS_MEM_NODE_SET_USED_FLAG(allocNode->selfNode.sizeAndFlag); ⑸ OS_MEM_ADD_USED(&pool->stat, OS_MEM_NODE_GET_SIZE(allocNode->selfNode.sizeAndFlag), OS_MEM_TASKID_GET(allocNode)); OsMemNodeDebugOperate(pool, allocNode, size); ⑹ return (allocNode + 1); }
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的冪。⑵處校驗下對齊內存后是否會數據溢出。⑶處計算對齊后需要申請的內存大小,然后判斷內存大小數值沒有已使用或已對齊標記。⑷處調用函數申請到內存VOID *ptr,然后計算出對齊的內存地址VOID *alignedPtr,如果二者相等則返回。⑸處計算出對齊內存的偏移值,⑹處獲取申請到的內存的控制節點,設置已對齊標記。⑺對偏移值設置對齊標記,然后把偏移值保存在內存VOID *alignedPtr的前4個字節里。⑻處重新定向要返回的指針,完成申請對齊的內存。
LITE_OS_SEC_TEXT VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary) { UINT32 useSize; UINT32 gapSize; VOID *ptr = NULL; VOID *alignedPtr = NULL; LosMemDynNode *allocNode = NULL; UINT32 intSave; ⑴ if ((pool == NULL) || (size == 0) || (boundary == 0) || !IS_POW_TWO(boundary) || !IS_ALIGNED(boundary, sizeof(VOID *))) { return NULL; } MEM_LOCK(intSave); do { ⑵ if ((boundary - sizeof(gapSize)) > ((UINT32)(-1) - size)) { break; } ⑶ useSize = (size + boundary) - sizeof(gapSize); if (OS_MEM_NODE_GET_USED_FLAG(useSize) || OS_MEM_NODE_GET_ALIGNED_FLAG(useSize)) { break; } ⑷ ptr = OsMemAllocWithCheck(pool, useSize); alignedPtr = (VOID *)OS_MEM_ALIGN(ptr, boundary); if (ptr == alignedPtr) { break; } ⑸ gapSize = (UINT32)((UINTPTR)alignedPtr - (UINTPTR)ptr); ⑹ allocNode = (LosMemDynNode *)ptr - 1; OS_MEM_NODE_SET_ALIGNED_FLAG(allocNode->selfNode.sizeAndFlag); #ifdef LOSCFG_MEM_RECORDINFO allocNode->selfNode.originSize = size; #endif #ifdef LOSCFG_MEM_HEAD_BACKUP OsMemNodeSaveWithGapSize(allocNode, gapSize); #endif ⑺ OS_MEM_NODE_SET_ALIGNED_FLAG(gapSize); *(UINT32 *)((UINTPTR)alignedPtr - sizeof(gapSize)) = gapSize; ⑻ ptr = alignedPtr; } while (0); #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordMalloc(ptr, size); #endif OsLmsSetAfterMalloc(ptr); MEM_UNLOCK(intSave); LOS_TRACE(MEM_ALLOC_ALIGN, pool, (UINTPTR)ptr, size, boundary); return ptr; }
2.5 釋放動態內存
對申請的內存塊使用完畢,我們可以使用函數UINT32 LOS_MemFree(VOID *pool, VOID *ptr)來釋放動態態內存,需要2個參數,VOID *pool是初始化過的動態內存池地址。VOID *ptr是需要釋放的動態內存塊的數據區的起始地址,注意這個不是內存控制節點的地址。下面分析下源碼。
⑴處對傳入的參數先進行校驗。⑵如果內存是從SLAB中申請的內存,需要釋放到SLAB內存區。⑶處調用函數OsMemFree(pool, ptr)完成內存的釋放。
LITE_OS_SEC_TEXT UINT32 LOS_MemFree(VOID *pool, VOID *ptr) { UINT32 ret; UINT32 intSave; ⑴ if ((pool == NULL) || (ptr == NULL) || !IS_ALIGNED(pool, sizeof(VOID *)) || !IS_ALIGNED(ptr, sizeof(VOID *))) { return LOS_NOK; } MEM_LOCK(intSave); ⑵ if (OsSlabMemFree(pool, ptr)) { ret = LOS_OK; goto OUT; } ⑶ ret = OsMemFree(pool, ptr); OUT: OsLmsSetAfterFree(ptr); MEM_UNLOCK(intSave); LOS_TRACE(MEM_FREE, pool, (UINTPTR)ptr); return ret; }
我們繼續分析下函數OsMemFree(pool, ptr)。⑴處獲取gapSize,對於函數LOS_MemAlloc()申請的內存,gapSize對應控制節點LosMemCtlNode的成員變量sizeAndFlag;對於函數LOS_MemAllocAlign()申請的內存,gapSize對應內存對齊偏移值。對於第一種情況,只標記已使用,第二種情況只標記已對齊。⑵處表示如果既標記已使用,又標記已對齊,則返回錯誤。⑶處獲取內存控制節點,對於第二種情況這樣的獲取是錯誤的。⑷處代碼校准第二種情況,如果gapSize標記為已對齊,去除gapSize中的對齊標記,獲取偏移值。⑸處對偏移值進行校驗,然后執行⑹獲取獲取內存控制節點。⑺處進一步調用函數完成內存的釋放。
UINT32 OsMemFree(VOID *pool, VOID *ptr) { UINT32 ret = LOS_NOK; UINT32 gapSize; LosMemDynNode *node = NULL; do { ⑴ gapSize = *(UINT32 *)((UINTPTR)ptr - sizeof(UINT32)); ⑵ if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize) && OS_MEM_NODE_GET_USED_FLAG(gapSize)) { PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize); goto OUT; } ⑶ node = (LosMemDynNode *)((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE); ⑷ if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize)) { gapSize = OS_MEM_NODE_GET_ALIGNED_GAPSIZE(gapSize); ⑸ if ((gapSize & (OS_MEM_ALIGN_SIZE - 1)) || (gapSize > ((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE))) { PRINT_ERR("illegal gapSize: 0x%x\n", gapSize); break; } ⑹ node = (LosMemDynNode *)((UINTPTR)ptr - gapSize - OS_MEM_NODE_HEAD_SIZE); } #ifndef LOSCFG_MEM_HEAD_BACKUP ⑺ ret = OsDoMemFree(pool, ptr, node); #endif } while (0); #ifdef LOSCFG_MEM_HEAD_BACKUP ret = OsMemBackupCheckAndRetore(pool, ptr, node); if (!ret) { ret = OsDoMemFree(pool, ptr, node); } #endif OUT: #ifdef LOSCFG_MEM_RECORDINFO if (ret == LOS_NOK) { OsMemRecordFree(ptr, 0); } #endif return ret; }
我們繼續看下函數OsDoMemFree(),該函數進一步調用函數OsMemFreeNode(node, pool)完成內存釋放。
LITE_OS_SEC_TEXT STATIC INLINE UINT32 OsDoMemFree(VOID *pool, const VOID *ptr, LosMemDynNode *node) { UINT32 ret = OsMemCheckUsedNode(pool, node); if (ret == LOS_OK) { #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordFree(ptr, node->selfNode.originSize); #endif OsMemFreeNode(node, pool); } return ret; }
函數OsMemFreeNode(node, pool)如下,繼續分析。⑴處獲取動態內存池的第一個內存控制節點,⑵處去除已使用標記。⑶處處理前一個節點不為空,且沒有使用的情況。⑷處執行內存節點合並,然后獲取下一個節點nextNode,如果下一個節點也是未使用節點,則把下一個節點從鏈表中刪除,並把空閑節點進行合並。⑸處把前一個節點從鏈表中刪除,基於合並后的內存節點大小重新掛載到鏈表上。
如果上一個節點已使用,無法和上一個節點合並,則執行⑹獲取下一個節點。如果下一個節點也是未使用節點,則把下一個節點從鏈表中刪除,並把空閑節點進行合並。⑺根據內存節點大小獲取鏈表節點,然后把釋放的內存節點掛載到鏈表上,完成內存節點的釋放。
STATIC INLINE VOID OsMemFreeNode(LosMemDynNode *node, LosMemPoolInfo *pool) { LosMemDynNode *nextNode = NULL; LOS_DL_LIST *listNodeHead = NULL; ⑴ const VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE); OS_MEM_REDUCE_USED(&pool->stat, OS_MEM_NODE_GET_SIZE(node->selfNode.sizeAndFlag), OS_MEM_TASKID_GET(node)); ⑵ node->selfNode.sizeAndFlag = OS_MEM_NODE_GET_SIZE(node->selfNode.sizeAndFlag); #ifdef LOSCFG_MEM_HEAD_BACKUP OsMemNodeSave(node); #endif #ifdef LOSCFG_MEM_LEAKCHECK OsMemLinkRegisterRecord(node); #endif ⑶ if ((node->selfNode.preNode != NULL) && !OS_MEM_NODE_GET_USED_FLAG(node->selfNode.preNode->selfNode.sizeAndFlag)) { LosMemDynNode *preNode = node->selfNode.preNode; ⑷ OsMemMergeNode(node); nextNode = OS_MEM_NEXT_NODE(preNode); if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag)) { OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode); OsMemMergeNode(nextNode); } ⑸ OsMemListDelete(&(preNode->selfNode.freeNodeInfo), firstNode); listNodeHead = OS_MEM_HEAD(pool, preNode->selfNode.sizeAndFlag); OS_CHECK_NULL_RETURN(listNodeHead); OsMemListAdd(listNodeHead, &preNode->selfNode.freeNodeInfo, firstNode); } else { ⑹ nextNode = OS_MEM_NEXT_NODE(node); if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag)) { OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode); OsMemMergeNode(nextNode); } ⑺ listNodeHead = OS_MEM_HEAD(pool, node->selfNode.sizeAndFlag); OS_CHECK_NULL_RETURN(listNodeHead); OsMemListAdd(listNodeHead, &node->selfNode.freeNodeInfo, firstNode); } }
2.6 重新申請動態內存
我們還可以使用函數VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size),按size大小重新分配內存塊,並將原內存塊內容拷貝到新內存塊。如果新內存塊申請成功,則釋放原內存塊。該函數需要3個參數,VOID *pool為內存池起始地址,VOID *ptr為之前申請的內存地址,UINT32 size為重新申請的內存大小。下面分析下源碼。
⑴處對參數進行校驗,內存池地址不能為空,內存大小不能含有已使用、已對齊標記。⑵處如果傳入的內存地址為空,則等價於LOS_MemAlloc()函數。⑶如果傳入size為0,等價於函數LOS_MemFree()。⑷如果開啟支持SLAB,需要調用OsMemReallocSlab()函數重新申請SLAB內存。⑸處調用函數OsMemRealloc()進行重新申請內存。
LITE_OS_SEC_TEXT_MINOR VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size) { UINT32 intSave; VOID *newPtr = NULL; BOOL isSlabMem = FALSE; ⑴ if (OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size) || (pool == NULL)) { return NULL; } ⑵ if (ptr == NULL) { newPtr = LOS_MemAlloc(pool, size); goto OUT; } ⑶ if (size == 0) { (VOID)LOS_MemFree(pool, ptr); goto OUT; } MEM_LOCK(intSave); ⑷ newPtr = OsMemReallocSlab(pool, ptr, &isSlabMem, size); if (isSlabMem == TRUE) { goto OUT_UNLOCK; } ⑸ newPtr = OsMemRealloc(pool, ptr, size); OUT_UNLOCK: MEM_UNLOCK(intSave); OUT: LOS_TRACE(MEM_REALLOC, pool, (UINTPTR)ptr, size); return newPtr; }
進一步看下函數OsMemRealloc()。⑴處獲取內存對齊后的大小allocSize,⑵處獲取內存對齊之前的地址,稍后會分析該函數OsGetRealPtr()。⑶獲取內存控制節點node,然后獲取節點的大小nodeSize。⑷處理重新申請的內存小於等於現有的內存的情況,需要調用函數OsMemReAllocSmaller()進行分割,分割完畢返回(VOID *)ptr即可。如果重新申請更大的內存,則執行⑸獲取下一個節點,然后⑹處理下一個節點可用且兩個節點大小之和大於等於重新申請內存的大小allocSize。執行⑺處的函數,合並節點重新分配內存。
如果連續的節點的大小不滿足重新申請內存的大小,則執行⑻處函數重新申請內存。然后執行⑼把之前內存的數據復制到新申請的內存區域,復制失敗的話,則把新申請的內存釋放掉,並返回退出函數。如果復制成功,繼續執行⑽釋放掉之前的節點。
STATIC VOID *OsMemRealloc(VOID *pool, VOID *ptr, UINT32 size) { LosMemDynNode *node = NULL; LosMemDynNode *nextNode = NULL; VOID *tmpPtr = NULL; VOID *realPtr = NULL; UINT32 nodeSize; ⑴ UINT32 allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE); #ifdef LOSCFG_MEM_RECORDINFO const VOID *originPtr = ptr; #endif ⑵ realPtr = OsGetRealPtr(pool, ptr); if (realPtr == NULL) { return NULL; } ⑶ node = (LosMemDynNode *)((UINTPTR)realPtr - OS_MEM_NODE_HEAD_SIZE); if (OsMemCheckUsedNode(pool, node) != LOS_OK) { #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordFree(originPtr, 0); #endif return NULL; } nodeSize = OS_MEM_NODE_GET_SIZE(node->selfNode.sizeAndFlag); ⑷ if (nodeSize >= allocSize) { #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordFree(originPtr, node->selfNode.originSize); #endif OsMemReAllocSmaller(pool, allocSize, node, nodeSize); #ifdef LOSCFG_MEM_RECORDINFO OsMemReallocNodeRecord(node, size, ptr); #endif return (VOID *)ptr; } ⑸ nextNode = OS_MEM_NEXT_NODE(node); ⑹ if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag) && ((nextNode->selfNode.sizeAndFlag + nodeSize) >= allocSize)) { #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordFree(originPtr, node->selfNode.originSize); #endif ⑺ OsMemMergeNodeForReAllocBigger(pool, allocSize, node, nodeSize, nextNode); #ifdef LOSCFG_MEM_RECORDINFO OsMemReallocNodeRecord(node, size, ptr); #endif return (VOID *)ptr; } ⑻ tmpPtr = OsMemAllocWithCheck(pool, size); if (tmpPtr != NULL) { #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordMalloc(tmpPtr, size); #endif UINT32 gapSize = (UINT32)((UINTPTR)ptr - (UINTPTR)realPtr); ⑼ if (memcpy_s(tmpPtr, size, ptr, (nodeSize - OS_MEM_NODE_HEAD_SIZE - gapSize)) != EOK) { (VOID)OsMemFree((VOID *)pool, (VOID *)tmpPtr); return NULL; } #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordFree(originPtr, node->selfNode.originSize); #endif ⑽ OsMemFreeNode(node, pool); } return tmpPtr; }
我們回過頭來,繼續看下函數OsGetRealPtr()。⑴獲取內存對齊的偏移值,⑵如果偏移值同時標記為已使用和已對齊,則返回錯誤。⑶如果偏移值標記為已對齊,則執行⑷去除對齊標記,獲取單純的偏移值。然后執行⑸,獲取內存對齊之前的數據區內存地址。
STATIC VOID *OsGetRealPtr(const VOID *pool, VOID *ptr) { VOID *realPtr = ptr; ⑴ UINT32 gapSize = *((UINT32 *)((UINTPTR)ptr - sizeof(UINT32))); ⑵ if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize) && OS_MEM_NODE_GET_USED_FLAG(gapSize)) { #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordFree(ptr, 0); #endif PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize); return NULL; } ⑶ if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize)) { ⑷ gapSize = OS_MEM_NODE_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); #ifdef LOSCFG_MEM_RECORDINFO OsMemRecordFree(ptr, 0); #endif return NULL; } ⑸ realPtr = (VOID *)((UINTPTR)ptr - (UINTPTR)gapSize); } return realPtr; }
小結
本文帶領大家一起剖析了LiteOS動態內存模塊bestfit算法的源代碼,包含動態內存的結構體、動態內存池初始化、動態內存申請、釋放等。感謝閱讀,如有任何問題、建議,都可以留言給我們:https://gitee.com/LiteOS/LiteOS/issues 。為了更容易找到LiteOS代碼倉,建議訪問 https://gitee.com/LiteOS/LiteOS ,關注Watch、點贊Star、並Fork到自己賬戶下,謝謝。
本文分享自華為雲社區《LiteOS內核源碼分析系列十三 動態內存Bestfit分配算法》,原文作者:zhushy 。
