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


    摘要:鸿蒙轻内核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
    ", 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
    ", (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
    ", 
                         (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
    ", 
                         (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),获取更多资讯

     

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

  • 相关阅读:
    【转载】设计模式_单例模式(学习)
    【转载】设计模式_抽象工厂模式(学习)
    【转载】设计模式_工厂方法(学习)
    【转载】设计模式_六大原则(学习)
    【转载】设计模式_简单工厂模式(学习)
    java有用的启动参数
    linux freetds操作mssql
    ansible 控制windows
    nginx 版本介绍
    tar 压缩去除目录
  • 原文地址:https://www.cnblogs.com/huaweiyun/p/15193072.html
Copyright © 2020-2023  润新知