摘要:鴻蒙輕核心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),獲取更多資訊

點選關注,第一時間瞭解華為雲新鮮技術~