摘要:本文帶領你們一塊兒剖析了LiteOS靜態內存模塊的源代碼,包含靜態內存的結構體、靜態內存池初始化、靜態內存申請、釋放、清除內容等。
本文分享自華爲雲社區《LiteOS內核源碼分析系列十二 靜態內存Static Memory》,原文做者:zhushy 。node
內存管理模塊管理系統的內存資源,它是操做系統的核心模塊之一,主要包括內存的初始化、分配以及釋放。git
在系統運行過程當中,內存管理模塊經過對內存的申請/釋放來管理用戶和OS對內存的使用,使內存的利用率和使用效率達到最優,同時最大限度地解決系統的內存碎片問題。算法
Huawei LiteOS的內存管理分爲靜態內存管理和動態內存管理,提供內存初始化、分配、釋放等功能。數組
- 動態內存:在動態內存池中分配用戶指定大小的內存塊。
- 優勢:按需分配。
- 缺點:內存池中可能出現碎片。
- 靜態內存:在靜態內存池中分配用戶初始化時預設(固定)大小的內存塊。
- 優勢:分配和釋放效率高,靜態內存池中無碎片。
- 缺點:只能申請到初始化預設大小的內存塊,不能按需申請。
本文主要分析LiteOS靜態內存(Memory Box),後續系列會繼續分析動態內存。靜態內存實質上是一個靜態數組,靜態內存池內的塊大小在初始化時設定,初始化後塊大小不可變動。靜態內存池由一個控制塊和若干相同大小的內存塊構成。控制塊位於內存池頭部,用於內存塊管理。內存塊的申請和釋放以塊大小爲粒度。ide
本文經過分析LiteOS靜態內存模塊的源碼,幫助讀者掌握靜態內存的使用。LiteOS靜態內存模塊的源代碼, 都可以在LiteOS
開源站點https://gitee.com/LiteOS/LiteOS 獲取。 靜態內存源代碼、開發文檔,示例程序代碼以下:函數
- LiteOS內核靜態內存源代碼
包括靜態內存的私有頭文件kernel\base\include\los_membox_pri.h、頭文件kernel\include\los_membox.h、C源代碼文件kernel\base\mem\membox\los_membox.c。源碼分析
- 開發指南文檔–內存
-
在線文檔https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E5%86%85%E5%AD%98。ui
接下來,咱們看下靜態內存的結構體,靜態內存初始化,靜態內存經常使用操做的源代碼。url
一、靜態內存結構體定義和經常使用宏定義
1.1 靜態內存結構體定義
在文件kernel\include\los_membox.h中,定義靜態內存池信息結構體爲LOS_MEMBOX_INFO,靜態內存節點LOS_MEMBOX_NODE結構體,源代碼以下,結構體成員的解釋見註釋部分。spa
typedef struct tagMEMBOX_NODE { struct tagMEMBOX_NODE *pstNext; /**< 靜態內存池中空閒節點指針,指向下一個空閒節點 */ } LOS_MEMBOX_NODE; typedef struct { UINT32 uwBlkSize; /**< 靜態內存池的內存塊大小 */ UINT32 uwBlkNum; /**< 靜態內存池的內存塊總數量 */ UINT32 uwBlkCnt; /**< 靜態內存池的已分配的內存塊總數量 */ #ifdef LOSCFG_KERNEL_MEMBOX_STATIC LOS_MEMBOX_NODE stFreeList; /**< 靜態內存池的空閒內存塊單向鏈表 */ #endif } LOS_MEMBOX_INFO;
對靜態內存使用以下示意圖進行說明,對一塊靜態內存區域,頭部是LOS_MEMBOX_INFO信息,接着是各個內存塊,每塊內存塊大小是uwBlkSize,包含內存塊節點LOS_MEMBOX_NODE和內存塊數據區。空閒內存塊節點指向下一塊空閒內存塊節點。
1.2 靜態內存經常使用宏定義
靜態內存頭文件中還提供了一些重要的宏定義。⑴處的LOS_MEMBOX_ALLIGNED(memAddr)用於對齊內存地址,⑵處OS_MEMBOX_NEXT(addr, blkSize)根據當前節點內存地址addr和內存塊大小blkSize獲取下一個內存塊的內存地址。⑶處OS_MEMBOX_NODE_HEAD_SIZE表示內存塊中節點頭大小,每一個內存塊包含內存節點LOS_MEMBOX_NODE和存放業務的數據區。⑷處表示靜態內存的總大小,包含內存池信息結構體佔用的大小,和各個內存塊佔用的大小。
⑴ #define LOS_MEMBOX_ALLIGNED(memAddr) (((UINTPTR)(memAddr) + sizeof(UINTPTR) - 1) & (~(sizeof(UINTPTR) - 1))) ⑵ #define OS_MEMBOX_NEXT(addr, blkSize) (LOS_MEMBOX_NODE *)(VOID *)((UINT8 *)(addr) + (blkSize)) ⑶ #define OS_MEMBOX_NODE_HEAD_SIZE sizeof(LOS_MEMBOX_NODE) ⑷ #define LOS_MEMBOX_SIZE(blkSize, blkNum) \ (sizeof(LOS_MEMBOX_INFO) + (LOS_MEMBOX_ALLIGNED((blkSize) + OS_MEMBOX_NODE_HEAD_SIZE) * (blkNum)))
在文件kernel\base\mem\membox\los_membox.c中也定義了一些宏。OS_MEMBOX_MAGIC定義魔術字,⑴處宏在內存塊節點從靜態內存池中分配出來後,節點指針.pstNext再也不指向下一個空閒內存塊節點,而是設置爲魔術字。⑵處的宏用於校驗魔術字。⑶處根據內存塊的節點地址獲取內存塊的數據區地址,⑷處根據內存塊的數據區地址獲取內存塊的節點地址。
#define OS_MEMBOX_MAGIC 0xa55a5aa5 ⑴ #define OS_MEMBOX_SET_MAGIC(addr) \ ((LOS_MEMBOX_NODE *)(addr))->pstNext = (LOS_MEMBOX_NODE *)OS_MEMBOX_MAGIC ⑵ #define OS_MEMBOX_CHECK_MAGIC(addr) \ ((((LOS_MEMBOX_NODE *)(addr))->pstNext == (LOS_MEMBOX_NODE *)OS_MEMBOX_MAGIC) ? LOS_OK : LOS_NOK) ⑶ #define OS_MEMBOX_USER_ADDR(addr) \ ((VOID *)((UINT8 *)(addr) + OS_MEMBOX_NODE_HEAD_SIZE)) ⑷ #define OS_MEMBOX_NODE_ADDR(addr) \ ((LOS_MEMBOX_NODE *)(VOID *)((UINT8 *)(addr) - OS_MEMBOX_NODE_HEAD_SIZE))
二、靜態內存經常使用操做
當用戶須要使用固定長度的內存時,能夠經過靜態內存分配的方式獲取內存,一旦使用完畢,經過靜態內存釋放函數歸還所佔用內存,使之能夠重複使用。
2.1 初始化靜態內存池
咱們分析下初始化靜態內存池函數UINT32 LOS_MemboxInit(VOID *pool, UINT32 poolSize, UINT32 blkSize)的代碼。咱們先看看函數參數,VOID *pool是靜態內存池的起始地址,UINT32 poolSize是初始化的靜態內存池的總大小,poolSize須要小於等於*pool開始的內存區域的大小,不然會影響後面的內存區域。還須要大於靜態內存的頭部大小sizeof(LOS_MEMBOX_INFO)。長度UINT32 blkSize是靜態內存池中的每一個內存塊的塊大小。
咱們看下代碼,⑴處對傳入參數進行校驗。⑵處設置靜態內存池中每一個內存塊的實際大小,已內存對齊,也算上內存塊中節點信息。⑶處計算內存池中內存塊的總數量,而後設置已用內存塊數量.uwBlkCnt爲0。⑷處若是可用的內存塊爲0,返回初始化失敗。⑸處獲取內存池中的第一個空閒內存塊節點。⑹處把空閒內存塊掛載在靜態內存池信息結構體空閒內存塊鏈表stFreeList.pstNext上,而後每一個空閒內存塊依次指向下一個空閒內存塊,連接起來。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_MemboxInit(VOID *pool, UINT32 poolSize, UINT32 blkSize) { LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool; LOS_MEMBOX_NODE *node = NULL; UINT32 index; UINT32 intSave; ⑴ if (pool == NULL) { return LOS_NOK; } if (blkSize == 0) { return LOS_NOK; } if (poolSize < sizeof(LOS_MEMBOX_INFO)) { return LOS_NOK; } MEMBOX_LOCK(intSave); ⑵ boxInfo->uwBlkSize = LOS_MEMBOX_ALLIGNED(blkSize + OS_MEMBOX_NODE_HEAD_SIZE); ⑶ boxInfo->uwBlkNum = (poolSize - sizeof(LOS_MEMBOX_INFO)) / boxInfo->uwBlkSize; boxInfo->uwBlkCnt = 0; ⑷ if ((boxInfo->uwBlkNum == 0) || (boxInfo->uwBlkSize < blkSize)) { MEMBOX_UNLOCK(intSave); return LOS_NOK; } ⑸ node = (LOS_MEMBOX_NODE *)(boxInfo + 1); ⑹ boxInfo->stFreeList.pstNext = node; for (index = 0; index < (boxInfo->uwBlkNum - 1); ++index) { node->pstNext = OS_MEMBOX_NEXT(node, boxInfo->uwBlkSize); node = node->pstNext; } node->pstNext = NULL; MEMBOX_UNLOCK(intSave); return LOS_OK; }
2.2 清除靜態內存塊內容
咱們可使用函數VOID LOS_MemboxClr(VOID *pool, VOID *box)來清除靜態內存塊內容,須要2個參數,VOID *pool是初始化過的靜態內存池地址。VOID *box是須要清除內容的靜態內存塊的數據區的起始地址,注意這個不是內存塊的節點地址,每一個內存塊的節點區不能清除。下面分析下源碼。
⑴處對參數進行校驗,⑵處調用memset_s()函數把內存塊的數據區寫入0。寫入的開始地址是內存塊的數據區的起始地址VOID *box,寫入長度是數據區的長度boxInfo->uwBlkSize - OS_MEMBOX_NODE_HEAD_SIZE。
LITE_OS_SEC_TEXT_MINOR VOID LOS_MemboxClr(VOID *pool, VOID *box) { LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool; UINT32 intSave; ⑴ if ((pool == NULL) || (box == NULL)) { return; } MEMBOX_LOCK(intSave); ⑵ (VOID)memset_s(box, (boxInfo->uwBlkSize - OS_MEMBOX_NODE_HEAD_SIZE), 0, (boxInfo->uwBlkSize - OS_MEMBOX_NODE_HEAD_SIZE)); MEMBOX_UNLOCK(intSave); }
2.3 申請、釋放靜態內存
初始化靜態內存池後,咱們可使用函數VOID *LOS_MemboxAlloc(VOID *pool)來申請靜態內存,下面分析下源碼。
⑴處獲取靜態內存池空閒內存塊鏈表頭結點,若是鏈表不爲空,執行⑵,把下一個可用節點賦值給nodeTmp。⑶處把鏈表頭結點執行下一個的下一個鏈表節點,而後執行⑷把分配出來的內存塊設置魔術字,接着把內存池已用內存塊數量加1。⑸處返回時調用宏OS_MEMBOX_USER_ADDR()計算出內存塊的數據區域地質。
LITE_OS_SEC_TEXT VOID *LOS_MemboxAlloc(VOID *pool) { LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool; LOS_MEMBOX_NODE *node = NULL; LOS_MEMBOX_NODE *nodeTmp = NULL; UINT32 intSave; if (pool == NULL) { return NULL; } MEMBOX_LOCK(intSave); ⑴ node = &(boxInfo->stFreeList); if (node->pstNext != NULL) { ⑵ nodeTmp = node->pstNext; ⑶ node->pstNext = nodeTmp->pstNext; ⑷ OS_MEMBOX_SET_MAGIC(nodeTmp); boxInfo->uwBlkCnt++; } MEMBOX_UNLOCK(intSave); ⑸ return (nodeTmp == NULL) ? NULL : OS_MEMBOX_USER_ADDR(nodeTmp); }
對申請的內存塊使用完畢,咱們可使用函數UINT32 LOS_MemboxFree(VOID *pool, VOID *box)來釋放靜態內存,須要2個參數,VOID *pool是初始化過的靜態內存池地址。VOID *box是須要釋放的靜態內存塊的數據區的起始地址,注意這個不是內存塊的節點地址。下面分析下源碼。
⑴處根據待釋放的內存塊的數據區域地址獲取節點地址node,⑵對要釋放的內存塊先進行校驗。⑶處把要釋放的內存塊掛在內存池空閒內存塊鏈表上,而後執行⑷把已用數量減1。
LITE_OS_SEC_TEXT UINT32 LOS_MemboxFree(VOID *pool, VOID *box) { LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool; UINT32 ret = LOS_NOK; UINT32 intSave; if ((pool == NULL) || (box == NULL)) { return LOS_NOK; } MEMBOX_LOCK(intSave); do { ⑴ LOS_MEMBOX_NODE *node = OS_MEMBOX_NODE_ADDR(box); ⑵ if (OsCheckBoxMem(boxInfo, node) != LOS_OK) { break; } ⑶ node->pstNext = boxInfo->stFreeList.pstNext; boxInfo->stFreeList.pstNext = node; ⑷ boxInfo->uwBlkCnt--; ret = LOS_OK; } while (0); MEMBOX_UNLOCK(intSave); return ret; }
接下來,咱們再看看校驗函數OsCheckBoxMem()。⑴若是內存池的塊大小爲0,返回校驗失敗。⑵處計算出要釋放的內存快節點相對第一個內存塊節點的偏移量offset。⑶若是偏移量除之內存塊數量餘數不爲0,返回校驗失敗。⑷若是偏移量除之內存塊數量的商大於等於內存塊的數量,返回校驗失敗。⑸調用宏OS_MEMBOX_CHECK_MAGIC校驗魔術字。
STATIC INLINE UINT32 OsCheckBoxMem(const LOS_MEMBOX_INFO *boxInfo, const VOID *node) { UINT32 offset; ⑴ if (boxInfo->uwBlkSize == 0) { return LOS_NOK; } ⑵ offset = (UINT32)((UINTPTR)node - (UINTPTR)(boxInfo + 1)); ⑶ if ((offset % boxInfo->uwBlkSize) != 0) { return LOS_NOK; } ⑷ if ((offset / boxInfo->uwBlkSize) >= boxInfo->uwBlkNum) { return LOS_NOK; } ⑸ return OS_MEMBOX_CHECK_MAGIC(node); }
三、Membox管理算法
Membox內存管理,支持靜態內存和動態內存,二選一,分別由宏開關LOSCFG_KERNEL_MEMBOX_STATIC和LOSCFG_KERNEL_MEMBOX_DYNAMIC進行控制。
Membox靜態內存是默認算法,須要開發者提供一個棧中的靜態內存區域;Membox動態內存支持從堆裏動態分配內存,相應的內存代碼在kernel\base\mem\membox\los_membox_dyn.c,代碼比較簡單,讀者們自行閱讀便可。