LiteOS內核源碼分析系列十三 動態內存Bestfit分配算法 (1)

      網友投稿 601 2022-05-30

      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。

      LiteOS內核源碼分析系列十三 動態內存Bestfit分配算法 (1)

      開發指南文檔–內存

      在線文檔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

      結構體源代碼如下,非常簡單,是一個長度為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); }

      輕量級操作系統 LiteOS

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:移動應用/APP的測試流程及方法
      下一篇:GraphQL技術怎樣?有什么缺點?
      相關文章
      色偷偷女男人的天堂亚洲网| 亚洲午夜精品久久久久久人妖| 亚洲精品自在线拍| 久久乐国产精品亚洲综合| 亚洲精品线路一在线观看| 一本色道久久88亚洲综合| 国产精品亚洲一区二区三区久久| 亚洲乱亚洲乱妇24p| 亚洲熟妇无码一区二区三区导航| 亚洲av无码国产综合专区| 亚洲av永久无码精品三区在线4 | 国外亚洲成AV人片在线观看| 国产成人亚洲精品影院| 亚洲中文字幕丝袜制服一区| 久久久久亚洲?V成人无码| 国产午夜亚洲不卡| 亚洲精品无码不卡在线播HE| 亚洲国产精彩中文乱码AV| 亚洲av无码潮喷在线观看| 久久久久亚洲精品成人网小说 | 亚洲人成网站在线播放vr| 亚洲成av人片天堂网| 亚洲AV日韩AV天堂一区二区三区| 亚洲成a人片在线观看中文动漫 | 亚洲第一第二第三第四第五第六| 色婷婷六月亚洲综合香蕉| 亚洲 小说区 图片区 都市| 亚洲伊人久久成综合人影院| 国产亚洲AV无码AV男人的天堂 | 亚洲AV成人无码久久WWW| 亚洲AV无码成H人在线观看| 国产精品亚洲美女久久久| 国产亚洲av片在线观看播放| 亚洲三级电影网站| 亚洲精品自拍视频| 亚洲欧美日韩自偷自拍| 午夜亚洲国产成人不卡在线| 亚洲熟女少妇一区二区| 婷婷亚洲久悠悠色悠在线播放| 亚洲第一香蕉视频| 亚洲国产乱码最新视频|