摘要:鴻蒙輕內核M核新增支持了多段非連續性內存區域,把多個非連續性內存邏輯上合一,用戶不感知底層的不同內存塊。
本文分享自華為雲社區《鴻蒙輕內核M核源碼分析系列九 動態內存Dynamic Memory 補充》,作者:zhushy。
一些芯片片內RAM大小無法滿足要求,需要使用片外物理內存進行擴充。對於多段非連續性內存,需要內存管理模塊統一管理,應用使用內存接口時不需要關注內存分配屬於哪塊物理內存,不感知多塊內存。
多段非連續性內存如下圖所示:
鴻蒙輕內核M核新增支持了多段非連續性內存區域,把多個非連續性內存邏輯上合一,用戶不感知底層的不同內存塊。本文來分析下動態內存模塊的支持多段非連續內存的源碼,幫助讀者掌握其使用。本文中所涉及的源碼,以OpenHarmony LiteOS-M內核為例,均可以在開源站點https://gitee.com/openharmony/kernel_liteos_m 獲取。接下來,我們看下新增的結構體、宏和對外接口的源代碼。
1、結構體定義和常用宏定義
在文件kernel/include/los_memory.h中新增了結構體LosMemRegion用於維護多個非連續的內存區域,包含各個內存區域的開始地址和大小。如下:
typedef struct { VOID *startAddress; /* 內存區域的開始地址 */ UINT32 length; /* 內存區域的長度 */ } LosMemRegion;
需要注意這個結構體的定義需要開啟宏LOSCFG_MEM_MUL_REGIONS的情況下才生效,這個宏也是支持非連續內存區域的配置宏,定義在文件kernel/include/los_config.h中。
我們繼續看下新增的幾個宏函數,定義在文件kernel/src/mm/los_memory.c,代碼下下文:
注釋講的比較明白,當開啟LOSCFG_MEM_MUL_REGIONS支持非連續內存特性時,會把兩個不連續內存區域之間的間隔Gap區域標記為虛擬的已使用內存節點。這個節點當然不能被釋放,在內存調測特性中也不能被統計。因為我們只是把它視為已使用內存節點,但其實不是。在動態內存算法中每個內存節點都維護一個指向前序節點的指針,對於虛擬已使用節點,我們把該指針設置為魔術字,來標記它是個內存區域的間隔部分。
⑴處定義了一個魔術字OS_MEM_GAP_NODE_MAGIC,用於表示兩個不連續內存區域之前的間隔Gap區域。⑵和⑶處定義2個宏,分別用於設置魔術字,驗證魔術字。
#if (LOSCFG_MEM_MUL_REGIONS == 1) /** * When LOSCFG_MEM_MUL_REGIONS is enabled to support multiple non-continuous memory regions, the gap between two memory regions * is marked as a used OsMemNodeHead node. The gap node could not be freed, and would also be skipped in some DFX functions. The * 'ptr.prev' pointer of this node is set to OS_MEM_GAP_NODE_MAGIC to identify that this is a gap node. */ ⑴ #define OS_MEM_GAP_NODE_MAGIC 0xDCBAABCD ⑵ #define OS_MEM_MARK_GAP_NODE(node) (((struct OsMemNodeHead *)(node))->ptr.prev = (struct OsMemNodeHead *)OS_MEM_GAP_NODE_MAGIC) ⑶ #define OS_MEM_IS_GAP_NODE(node) (((struct OsMemNodeHead *)(node))->ptr.prev == (struct OsMemNodeHead *)OS_MEM_GAP_NODE_MAGIC) #else ⑵ #define OS_MEM_MARK_GAP_NODE(node) ⑶ #define OS_MEM_IS_GAP_NODE(node) FALSE #endif
2、動態內存常用操作
本節我們一起分析下非連續性內存的實現算法,及接口實現代碼。首先通過示意圖了解下算法:
集合示意圖,我們了解下非連續性內存合並為一個內存池的步驟:
- 1、把多段內存區域的第一塊內存區域調用LOS_MemInit進行初始化
- 2、獲取下一個內存區域的開始地址和長度,計算該內存區域和上一塊內存區域的間隔大小gapSize。
- 3、把內存塊間隔部分視為虛擬的已使用節點,使用上一內存塊的尾節點,設置其大小為gapSize+ OS_MEM_NODE_HEAD_SIZE。
- 4、把當前內存區域划分為一個空閑內存塊和一個尾節點,把空閑內存塊插入到空閑鏈表。並設置各個節點的前后鏈接關系。
- 5、有更多的非連續內存塊,重復上述步驟2-4。
2.1 新增接口LOS_MemRegionsAdd
新增的接口的接口說明文檔見下文,注釋比較詳細,總結如下:
- LOSCFG_MEM_MUL_REGIONS=0:
不支持多段非連續內存,相關代碼不使能。
- LOSCFG_MEM_MUL_REGIONS=1:
支持多段非連續內存,相關代碼使能。用戶配置多段內存區域,調用接口
LOS_MemRegionsAdd(VOID *pool, const LosMemRegion * const multipleMemRegions)進行內存池合一:
- 如果pool為空,則合並到主內存堆m_aucSysMem0。
- 如果不為空,則初始化一個新的內存池,合並多內存區域為一個從堆。
/** * @ingroup los_memory * @brief Initialize multiple non-continuous memory regions. * * @par Description: * <ul> * <li>This API is used to initialize multiple non-continuous memory regions. If the starting address of a pool is specified, * the memory regions will be linked to the pool as free nodes. Otherwise, the first memory region will be initialized as a * new pool, and the rest regions will be linked as free nodes to the new pool.</li> * </ul> * * @attention * <ul> * <li>If the starting address of a memory pool is specified, the start address of the non-continuous memory regions should be * greater than the end address of the memory pool.</li> * <li>The multiple non-continuous memory regions shouldn't conflict with each other.</li> * </ul> * * @param pool [IN] The memory pool address. If NULL is specified, the start address of first memory region will be * initialized as the memory pool address. If not NULL, it should be a valid address of a memory pool. * @param memRegions [IN] The LosMemRegion array that contains multiple non-continuous memory regions. The start address * of the memory regions are placed in ascending order. * @param memRegionCount [IN] The count of non-continuous memory regions, and it should be the length of the LosMemRegion array. * * @retval #LOS_NOK The multiple non-continuous memory regions fails to be initialized. * @retval #LOS_OK The multiple non-continuous memory regions is initialized successfully. * @par Dependency: * <ul> * <li>los_memory.h: the header file that contains the API declaration.</li> * </ul> * @see None. */ extern UINT32 LOS_MemRegionsAdd(VOID *pool, const LosMemRegion * const memRegions, UINT32 memRegionCount);
2.2 新增接口LOS_MemRegionsAdd實現
結合上文示意圖,加上注釋,實現比較清晰,直接閱讀下代碼即可。
#if (LOSCFG_MEM_MUL_REGIONS == 1) STATIC INLINE UINT32 OsMemMulRegionsParamCheck(VOID *pool, const LosMemRegion * const memRegions, UINT32 memRegionCount) { const LosMemRegion *memRegion = NULL; VOID *lastStartAddress = NULL; VOID *curStartAddress = NULL; UINT32 lastLength; UINT32 curLength; UINT32 regionCount; if ((pool != NULL) && (((struct OsMemPoolHead *)pool)->info.pool != pool)) { PRINT_ERR("wrong mem pool addr: %p, func: %s, line: %d\n", pool, __FUNCTION__, __LINE__); return LOS_NOK; } if (pool != NULL) { lastStartAddress = pool; lastLength = ((struct OsMemPoolHead *)pool)->info.totalSize; } memRegion = memRegions; regionCount = 0; while (regionCount < memRegionCount) { curStartAddress = memRegion->startAddress; curLength = memRegion->length; if ((curStartAddress == NULL) || (curLength == 0)) { PRINT_ERR("Memory address or length configured wrongly:address:0x%x, the length:0x%x\n", (UINTPTR)curStartAddress, curLength); return LOS_NOK; } if (((UINTPTR)curStartAddress & (OS_MEM_ALIGN_SIZE - 1)) || (curLength & (OS_MEM_ALIGN_SIZE - 1))) { PRINT_ERR("Memory address or length configured not aligned:address:0x%x, the length:0x%x, alignsize:%d\n", \ (UINTPTR)curStartAddress, curLength, OS_MEM_ALIGN_SIZE); return LOS_NOK; } if ((lastStartAddress != NULL) && (((UINT8 *)lastStartAddress + lastLength) >= (UINT8 *)curStartAddress)) { PRINT_ERR("Memory regions overlapped, the last start address:0x%x, the length:0x%x, the current start address:0x%x\n", \ (UINTPTR)lastStartAddress, lastLength, (UINTPTR)curStartAddress); return LOS_NOK; } memRegion++; regionCount++; lastStartAddress = curStartAddress; lastLength = curLength; } return LOS_OK; } STATIC INLINE VOID OsMemMulRegionsLink(struct OsMemPoolHead *poolHead, VOID *lastStartAddress, UINT32 lastLength, struct OsMemNodeHead *lastEndNode, const LosMemRegion *memRegion) { UINT32 curLength; UINT32 gapSize; struct OsMemNodeHead *curEndNode = NULL; struct OsMemNodeHead *curFreeNode = NULL; VOID *curStartAddress = NULL; curStartAddress = memRegion->startAddress; curLength = memRegion->length; // mark the gap between two regions as one used node gapSize = (UINT8 *)(curStartAddress) - ((UINT8 *)(lastStartAddress) + lastLength); lastEndNode->sizeAndFlag = gapSize + OS_MEM_NODE_HEAD_SIZE; OS_MEM_SET_MAGIC(lastEndNode); OS_MEM_NODE_SET_USED_FLAG(lastEndNode->sizeAndFlag); // mark the gap node with magic number OS_MEM_MARK_GAP_NODE(lastEndNode); poolHead->info.totalSize += (curLength + gapSize); poolHead->info.totalGapSize += gapSize; curFreeNode = (struct OsMemNodeHead *)curStartAddress; curFreeNode->sizeAndFlag = curLength - OS_MEM_NODE_HEAD_SIZE; curFreeNode->ptr.prev = lastEndNode; OS_MEM_SET_MAGIC(curFreeNode); OsMemFreeNodeAdd(poolHead, (struct OsMemFreeNodeHead *)curFreeNode); curEndNode = OS_MEM_END_NODE(curStartAddress, curLength); curEndNode->sizeAndFlag = 0; curEndNode->ptr.prev = curFreeNode; OS_MEM_SET_MAGIC(curEndNode); OS_MEM_NODE_SET_USED_FLAG(curEndNode->sizeAndFlag); #if (LOSCFG_MEM_WATERLINE == 1) poolHead->info.curUsedSize += OS_MEM_NODE_HEAD_SIZE; poolHead->info.waterLine = poolHead->info.curUsedSize; #endif } UINT32 LOS_MemRegionsAdd(VOID *pool, const LosMemRegion *const memRegions, UINT32 memRegionCount) { UINT32 ret; UINT32 lastLength; UINT32 curLength; UINT32 regionCount; struct OsMemPoolHead *poolHead = NULL; struct OsMemNodeHead *lastEndNode = NULL; struct OsMemNodeHead *firstFreeNode = NULL; const LosMemRegion *memRegion = NULL; VOID *lastStartAddress = NULL; VOID *curStartAddress = NULL; ret = OsMemMulRegionsParamCheck(pool, memRegions, memRegionCount); if (ret != LOS_OK) { return ret; } memRegion = memRegions; regionCount = 0; if (pool != NULL) { // add the memory regions to the specified memory pool poolHead = (struct OsMemPoolHead *)pool; lastStartAddress = pool; lastLength = poolHead->info.totalSize; } else { // initialize the memory pool with the first memory region lastStartAddress = memRegion->startAddress; lastLength = memRegion->length; poolHead = (struct OsMemPoolHead *)lastStartAddress; ret = LOS_MemInit(lastStartAddress, lastLength); if (ret != LOS_OK) { return ret; } memRegion++; regionCount++; } firstFreeNode = OS_MEM_FIRST_NODE(lastStartAddress); lastEndNode = OS_MEM_END_NODE(lastStartAddress, lastLength); while (regionCount < memRegionCount) { // traverse the rest memory regions, and initialize them as free nodes and link together curStartAddress = memRegion->startAddress; curLength = memRegion->length; OsMemMulRegionsLink(poolHead, lastStartAddress, lastLength, lastEndNode, memRegion); lastStartAddress = curStartAddress; lastLength = curLength; lastEndNode = OS_MEM_END_NODE(curStartAddress, curLength); memRegion++; regionCount++; } firstFreeNode->ptr.prev = lastEndNode; return ret; } #endif
小結
本文帶領大家一起剖析了鴻蒙輕內核M核的動態內存如何支持多段非連續性內存,包含結構體、運作示意圖、新增接口等等。感謝閱讀,如有任何問題、建議,都可以留言評論,謝謝。
更多學習內容,請點擊關注IoT物聯網社區,添加華為雲IoT小助手微信號(hwc-iot),獲取更多資訊