3

深层剖析鸿蒙轻内核M核的动态内存如何支持多段非连续性内存

 3 years ago
source link: https://segmentfault.com/a/1190000040582360
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

深层剖析鸿蒙轻内核M核的动态内存如何支持多段非连续性内存

发布于 28 分钟前

摘要:鸿蒙轻内核M核新增支持了多段非连续性内存区域,把多个非连续性内存逻辑上合一,用户不感知底层的不同内存块。

本文分享自华为云社区《鸿蒙轻内核M核源码分析系列九 动态内存Dynamic Memory 补充》,作者:zhushy。

一些芯片片内RAM大小无法满足要求,需要使用片外物理内存进行扩充。对于多段非连续性内存,需要内存管理模块统一管理,应用使用内存接口时不需要关注内存分配属于哪块物理内存,不感知多块内存。

多段非连续性内存如下图所示:

鸿蒙轻内核M核新增支持了多段非连续性内存区域,把多个非连续性内存逻辑上合一,用户不感知底层的不同内存块。本文来分析下动态内存模块的支持多段非连续内存的源码,帮助读者掌握其使用。本文中所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点https://gitee.com/openharmony... 获取。接下来,我们看下新增的结构体、宏和对外接口的源代码。

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、动态内存常用操作

本节我们一起分析下非连续性内存的实现算法,及接口实现代码。首先通过示意图了解下算法:
image.png

集合示意图,我们了解下非连续性内存合并为一个内存池的步骤:

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),获取更多资讯

点击关注,第一时间了解华为云新鲜技术~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK