LiteOS内核源码分析系列十三 动态内存Bestfit分配算法 (1)

举报
zhushy 发表于 2021/04/21 09:50:04 2021/04/21
【摘要】 LiteOS内核源码分析系列十三 动态内存Bestfit分配算法内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。Huawei LiteOS的内存管理分为静态内存管理和动态内存管理,提供内存初...

LiteOS内核源码分析系列十三 动态内存Bestfit分配算法

内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。

在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。

Huawei LiteOS的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。

  • 动态内存:在动态内存池中分配用户指定大小的内存块。

    • 优点:按需分配。
    • 缺点:内存池中可能出现碎片。
  • 静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。

    • 优点:分配和释放效率高,静态内存池中无碎片。
    • 缺点:只能申请到初始化预设大小的内存块,不能按需申请。

上一系列分析了静态内存,我们开始分析动态内存。动态内存管理主要用于用户需要使用大小不等的内存块的场景。当用户需要使用内存时,可以通过操作系统的动态内存申请函数索取指定大小的内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。

LiteOS动态内存支持bestfit(也称为dlink)和bestfit_little两种内存管理算法。本文主要分析LiteOS动态内存的bestfit算法,后续系列会继续分析动态内存的bestfit_little的算法。

本文通过分析LiteOS动态内存模块的源码,帮助读者掌握动态内存的使用。LiteOS动态内存模块的源代码,均可以在LiteOS开源站点https://gitee.com/LiteOS/LiteOS 获取。动态内存bestfit算法的源代码、开发文档,示例程序代码如下:


接下来,我们看下动态内存的结构体,动态内存初始化,动态内存常用操作的源代码。

1、动态内存结构体定义和常用宏定义

1.1 动态内存结构体定义

动态内存bestfit算法的结构体有动态内存池信息结构体LosMemPoolInfo,多双向链表表头结构体LosMultipleDlinkHead、动态内存链表节点结构体LosMemDynNode,内存链表控制节点结构体LosMemCtlNode

对动态内存使用如下示意图进行说明,对一块动态内存区域,第一部分是内存池信息结构体LosMemPoolInfo,接着是第二部分多双向链表表头结构体LosMultipleDlinkHead,第三部分是动态内存链表节点结构体LosMemDynNode,内存链表控制节点结构体LosMemCtlNode。使用动态内存的bestfit算法初始化后,第三部分包含2个内存块,第一个内存块包含内存链表控制节点结构体LosMemCtlNode和内存块数据区,尾节点只包含内存链表控制节点结构体LosMemCtlNode,没有数据区。控制节点结构体LosMemCtlNode持有上一个内存块的指针。有数据区的内存块会挂载在第二部分的链表上。当申请内存时,根据需要内存的大小,从第二部分的链表获取合适的内存块,如果内存块超出需求,会进行切割,剩余的部分和后续的空闲节点合并,重新挂载到第二部分的链表上。

LOS_DynMem_Bestfit

1.1.1 动态内存池信息结构体LosMemPoolInfo

在文件kernel\base\include\los_memory_pri.h中,定义了内存池信息结构体LosMemPoolInfo。这是动态内存池的第一部分,维护内存池的开始地址和大小信息。动态内存bestfit算法和bestfit_little算法中都定义了该结构体,结构体名称一样,成员有差异,我们先看看bestfit算法的结构体,源代码如下。两个主要的成员是内存池开始地址.pool和内存池大小.poolSize。其他结构体需要开启相应的宏才生效,暂不讨论这些宏相关的特性。

typedef struct {
    VOID        *pool;      /* 内存池的内存开始地址 */
    UINT32      poolSize;   /* 内存池大小 */

#ifdef LOSCFG_MEM_TASK_STAT
    Memstat     stat;
#endif

#ifdef LOSCFG_MEM_MUL_POOL
    VOID        *nextPool;
#endif

#ifdef LOSCFG_KERNEL_MEM_SLAB_EXTENTION
    struct LosSlabControlHeader slabCtrlHdr;
#endif
} LosMemPoolInfo;

1.1.2 多双向链表表头结构体LosMultipleDlinkHead

在文件kernel\base\include\los_multipledlinkhead_pri.h中,定义了内存池多双向链表表头结构体LosMultipleDlinkHead。这是动态内存池的第二部分,结构体本身是一个数组,每个元素是一个双向链表,所有free节点的控制头都会被分类挂在这个数组的双向链表中。

假设内存允许的最小节点为2^min字节,则数组的第一个双向链表存储的是所有size2^min<size< 2^(min+1)free节点,第二个双向链表存储的是所有size2^(min+1)<size< 2^(min+2)free节点,依次类推第n个双向链表存储的是所有size2^(min+n-1)<size< 2^(min+n)free节点。每次申请内存的时候,会从这个数组检索最合适大小的free节点以分配内存。每次释放内存时,会将该内存作为free节点存储至这个数组以便下次再使用。

结构体源代码如下,非常简单,是一个长度为OS_MULTI_DLNK_NUM的双向链表数组。

typedef struct {
    LOS_DL_LIST listHead[OS_MULTI_DLNK_NUM];
} LosMultipleDlinkHead;

我们再看看和结构体LosMultipleDlinkHead相关的宏定义,OS_MIN_MULTI_DLNK_LOG2OS_MAX_MULTI_DLNK_LOG2指定了双向链表中存储的内存节点的大小访问,第一个存储大小在[2^4,2^5)的空闲内存节点,依次类推,第26个即OS_MULTI_DLNK_NUM存储大小在[2^29,2^30)的空闲内存节点。多链表表头结构体占用的内存大小为OS_DLNK_HEAD_SIZE

#define OS_MAX_MULTI_DLNK_LOG2  29
#define OS_MIN_MULTI_DLNK_LOG2  4
#define OS_MULTI_DLNK_NUM       ((OS_MAX_MULTI_DLNK_LOG2 - OS_MIN_MULTI_DLNK_LOG2) + 1)
#define OS_DLNK_HEAD_SIZE       OS_MULTI_DLNK_HEAD_SIZE
#define OS_MULTI_DLNK_HEAD_SIZE sizeof(LosMultipleDlinkHead)

1.1.3 动态内存链表节点结构体LosMemDynNode和链表控制节点结构体LosMemCtlNode

在文件kernel\base\mem\bestfit\los_memory_internal.h中定义2个结构体,动态内存链表节点结构体LosMemDynNode和链表控制节点结构体LosMemCtlNode。这是动态内存池的第三部分,占用内存池极大部分的空间,是用于存放各节点的实际区域。设计2个结构体的原因为满足备份内存链表节点的需要。可以看出开启备份链表节点的宏LOSCFG_MEM_HEAD_BACKUP时,LosMemDynNode结构体包含2个LosMemCtlNode,一个是.backupNode,另外一个是.selfNode。2个结构体源码如下,重要的成员的解释见注释部分。

/* 内存链表控制节点结构体 */
typedef struct {
    union {
        LOS_DL_LIST freeNodeInfo;         /* 空闲内存链表节点 */
        struct {
            UINT32 magic;                 /* 魔术字 */
            UINT32 taskId   : 16;         /* 使用内存的任务Id */
#ifdef LOSCFG_MEM_MUL_MODULE
            UINT32 moduleId : 16;
#endif
        };
    };

    struct tagLosMemDynNode *preNode;   /* 指针,指针前一个内存节点 */

#ifdef LOSCFG_MEM_HEAD_BACKUP
    UINT32 gapSize;
    UINTPTR checksum; /* magic = xor checksum */
#endif

#ifdef LOSCFG_MEM_RECORDINFO
    UINT32 originSize;
#ifdef LOSCFG_AARCH64
    UINT32 reserve1; /* 64-bit alignment */
#endif
#endif

#ifdef LOSCFG_MEM_LEAKCHECK
    UINTPTR linkReg[LOS_RECORD_LR_CNT];
#endif

#ifdef LOSCFG_AARCH64
    UINT32 reserve2; /* 64-bit alignment */
#endif
    UINT32 sizeAndFlag;                     /* 大小和标志,高2位用作标记,其余位表示大小 */
} LosMemCtlNode;

/* 内存链表节点结构体 */
typedef struct tagLosMemDynNode {
#ifdef LOSCFG_MEM_HEAD_BACKUP
    LosMemCtlNode backupNode;
#endif
    LosMemCtlNode selfNode;
} LosMemDynNode;

1.2 动态内存常用宏定义

动态内存头文件kernel\base\mem\bestfit\los_memory_internal.h中还提供了一些重要的宏定义,这些宏非常重要,在分析源代码前需要熟悉下这些宏的定义。

⑴处的OS_MEM_ALIGN(p, alignSize)用于对齐内存地址,⑵处OS_MEM_NODE_HEAD_SIZE表示一个内存链表节点的大小,OS_MEM_MIN_POOL_SIZE表示一个动态内存池的最小大小,包含一个内存池信息结构体大小,1个多链表表头结构体大小,和2个链表节点大小。⑶处IS_POW_TWO(value)判断value是否是2的幂。⑷处定义内存池地址对齐值,内存节点对齐值。

⑸处定义是否使用、是否对齐2个标记位,分别是高31位,和高30位。然后分别定义3个宏函数,用于获取是否已使用/对齐,设置标记为使用/对齐,获取去除标记后的使用大小。

⑹处OS_MEM_HEAD_ADDR(pool)表示动态内存池第二部分多链表表头结构体的开始地址。宏函数OS_MEM_HEAD(pool, size)用于计算大小为size的内存块对应的多链表表头的地址,其实就是把内存池第三部分的内存块的大小映射到第二部分的链表位置上。其中调用的函数OsDLnkMultiHead()后文我们再分析。

⑺处定义内存节点操作相关的宏。OS_MEM_NEXT_NODE(node)获取内存节点的下一个内存节点,OS_MEM_FIRST_NODE(pool)获取内存池中第一个内存节点,OS_MEM_END_NODE(pool, size)获取内存池中最后一个内存节点。

⑻处定义2个宏,判断一个内存地址是否处于指定的区间,两者区别是是否开闭区间。⑼处的2个宏对边界内存节点设置魔术字和魔术字校验。

⑴  #define OS_MEM_ALIGN(p, alignSize)          (((UINTPTR)(p) + (alignSize) - 1) & ~((UINTPTR)((alignSize) - 1)))

⑵  #define OS_MEM_NODE_HEAD_SIZE               sizeof(LosMemDynNode)
    #define OS_MEM_MIN_POOL_SIZE                (OS_DLNK_HEAD_SIZE + (2 * OS_MEM_NODE_HEAD_SIZE) + sizeof(LosMemPoolInfo))

⑶  #define IS_POW_TWO(value)                   ((((UINTPTR)(value)) & ((UINTPTR)(value) - 1)) == 0)

⑷  #define POOL_ADDR_ALIGNSIZE                 64
    #ifdef LOSCFG_AARCH64
    #define OS_MEM_ALIGN_SIZE                   8
    #else
    #define OS_MEM_ALIGN_SIZE                   4
    #endif

⑸  #define OS_MEM_NODE_USED_FLAG               0x80000000U
    #define OS_MEM_NODE_ALIGNED_FLAG            0x40000000U
    #define OS_MEM_NODE_ALIGNED_AND_USED_FLAG   (OS_MEM_NODE_USED_FLAG | OS_MEM_NODE_ALIGNED_FLAG)
    #define OS_MEM_NODE_GET_ALIGNED_FLAG(sizeAndFlag) \
        ((sizeAndFlag) & OS_MEM_NODE_ALIGNED_FLAG)
    #define OS_MEM_NODE_SET_ALIGNED_FLAG(sizeAndFlag) \
        ((sizeAndFlag) = ((sizeAndFlag) | OS_MEM_NODE_ALIGNED_FLAG))
    #define OS_MEM_NODE_GET_ALIGNED_GAPSIZE(sizeAndFlag) \
        ((sizeAndFlag) & ~OS_MEM_NODE_ALIGNED_FLAG)
    #define OS_MEM_NODE_GET_USED_FLAG(sizeAndFlag) \
        ((sizeAndFlag) & OS_MEM_NODE_USED_FLAG)
    #define OS_MEM_NODE_SET_USED_FLAG(sizeAndFlag) \
        ((sizeAndFlag) = ((sizeAndFlag) | OS_MEM_NODE_USED_FLAG))
    #define OS_MEM_NODE_GET_SIZE(sizeAndFlag) \
        ((sizeAndFlag) & ~OS_MEM_NODE_ALIGNED_AND_USED_FLAG)

⑹  #define OS_MEM_HEAD(pool, size) \
        OsDLnkMultiHead(OS_MEM_HEAD_ADDR(pool), size)
    #define OS_MEM_HEAD_ADDR(pool) \
        ((VOID *)((UINTPTR)(pool) + sizeof(LosMemPoolInfo)))

⑺  #define OS_MEM_NEXT_NODE(node) \
        ((LosMemDynNode *)(VOID *)((UINT8 *)(node) + OS_MEM_NODE_GET_SIZE((node)->selfNode.sizeAndFlag)))
    #define OS_MEM_FIRST_NODE(pool) \
        ((LosMemDynNode *)(VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE))
    #define OS_MEM_END_NODE(pool, size) \
        ((LosMemDynNode *)(VOID *)(((UINT8 *)(pool) + (size)) - OS_MEM_NODE_HEAD_SIZE))

⑻  #define OS_MEM_MIDDLE_ADDR_OPEN_END(startAddr, middleAddr, endAddr) \
        (((UINT8 *)(startAddr) <= (UINT8 *)(middleAddr)) && ((UINT8 *)(middleAddr) < (UINT8 *)(endAddr)))
    #define OS_MEM_MIDDLE_ADDR(startAddr, middleAddr, endAddr) \
        (((UINT8 *)(startAddr) <= (UINT8 *)(middleAddr)) && ((UINT8 *)(middleAddr) <= (UINT8 *)(endAddr)))

⑼  #define OS_MEM_SET_MAGIC(value) \
        (value) = (UINT32)((UINTPTR)&(value) ^ (UINTPTR)(-1))
    #define OS_MEM_MAGIC_VALID(value) \
        (((UINTPTR)(value) ^ (UINTPTR)&(value)) == (UINTPTR)(-1))

我们看下宏中调用的函数OsDLnkMultiHead(),函数定义在文件kernel\base\mem\bestfit\los_multipledlinkhead.c中。该函数需要2个参数,VOID *headAddr为第二部分的多链表数组的起始地址,UINT32 size为内存块的大小。该函数把内存池第三部分的内存块的大小映射到第二部分的链表位置上,我们分析下代码。

⑴处的函数OsLog2()名称中的Log是对数英文logarithm的缩写,函数用于计算以2为底的对数的整数部分,输入参数是内存池第三部分的内存块的大小size,输出是第二部分多链表数组的数组索引,见代码片段⑵。⑶处如果索引大于OS_MAX_MULTI_DLNK_LOG2,无法分配这么大的内存块,返回NULL。⑷处如果索引小于等于OS_MIN_MULTI_DLNK_LOG2,则使用最小值作为索引。⑸处返回多链表表头中的链表头节点。


STATIC INLINE UINT32 OsLog2(UINT32 size)
{return (size > 0) ? (UINT32)LOS_HighBitGet(size) : 0;
}

LITE_OS_SEC_TEXT_MINOR LOS_DL_LIST *OsDLnkMultiHead(VOID *headAddr, UINT32 size)
{
    LosMultipleDlinkHead *dlinkHead = (LosMultipleDlinkHead *)headAddr;
⑵   UINT32 index = OsLog2(size);
    if (index > OS_MAX_MULTI_DLNK_LOG2) {return NULL;
    } else if (index <= OS_MIN_MULTI_DLNK_LOG2) {
⑷      index = OS_MIN_MULTI_DLNK_LOG2;
    }return dlinkHead->listHead + (index - OS_MIN_MULTI_DLNK_LOG2);
}

2、动态内存常用操作

Huawei LiteOS系统中的动态内存管理模块为用户提供初始化和删除内存池、申请、释放动态内存等操作,我们来分析下接口的源代码。在分析下内存操作接口之前,我们先看下一下常用的内部接口。

2.1 动态内存内部接口

2.1.1 清除内存节点内容

函数VOID OsMemClearNode(LosMemDynNode *node)用于清除给定的内存节点内容,设置内存数据内容为0。代码比较简单,直接调用函数memset_s()完成操作。

STATIC INLINE VOID OsMemClearNode(LosMemDynNode *node)
{
    (VOID)memset_s((VOID *)node, sizeof(LosMemDynNode), 0, sizeof(LosMemDynNode));
}

2.1.2 合并内存节点

函数VOID OsMemMergeNode(LosMemDynNode *node)用于合并给定节点LosMemDynNode *node和它前一个空闲节点,然后清除给定节点的内容。⑴处把前一个节点的大小加上要合入节点的大小。⑵处获取给定节点的下一个节点,然后把它的前一个节点指向给定节点的前一个节点。⑶处清除给定节点的内容,完成节点的合并。

STATIC INLINE VOID OsMemMergeNode(LosMemDynNode *node)
{
    LosMemDynNode *nextNode = NULL;

⑴  node->selfNode.preNode->selfNode.sizeAndFlag += node->selfNode.sizeAndFlag;
⑵  nextNode = (LosMemDynNode *)((UINTPTR)node + node->selfNode.sizeAndFlag);
    nextNode->selfNode.preNode = node->selfNode.preNode;
#ifdef LOSCFG_MEM_HEAD_BACKUP
    OsMemNodeSave(node->selfNode.preNode);
    OsMemNodeSave(nextNode);
#endifOsMemClearNode(node);
}

2.1.3 分割内存节点

函数VOID OsMemSplitNode(VOID *pool, LosMemDynNode *allocNode, UINT32 allocSize)用于分割内存节点,需要三个参数。VOID *pool是内存池起始地址,LosMemDynNode *allocNode表示从该内存节点分配出需要的内存,UINT32 allocSize是需要分配的内存大小。分割之后剩余的部分,如果下一个节点是空闲节点,则合并一起。分割剩余的节点会挂载到内存第二部分的多链表上。

⑴处获取动态内存池的第一个内存控制节点,⑵处表示newFreeNode是分配之后剩余的空闲内存节点,设置它的上一个节点为分配的节点,并设置剩余内存大小。⑶处获取下一个节点,下一个节点的前一个节点设置为新的空闲节点newFreeNode。⑷处判断下一个节点是否被使用,如果没有使用,则把下一个节点从链表中删除,然后和空闲节点newFreeNode合并。⑸处根据空闲节点newFreeNode的大小获取对应的链表头节点,然后执行⑹把空闲内存节点挂载到链表上。

STATIC INLINE VOID OsMemSplitNode(VOID *pool,
                                  LosMemDynNode *allocNode, UINT32 allocSize)
{
    LosMemDynNode *newFreeNode = NULL;
    LosMemDynNode *nextNode = NULL;
    LOS_DL_LIST *listNodeHead = NULL;const VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE);

⑵  newFreeNode = (LosMemDynNode *)(VOID *)((UINT8 *)allocNode + allocSize);
    newFreeNode->selfNode.preNode = allocNode;
    newFreeNode->selfNode.sizeAndFlag = allocNode->selfNode.sizeAndFlag - allocSize;
    allocNode->selfNode.sizeAndFlag = allocSize;
⑶  nextNode = OS_MEM_NEXT_NODE(newFreeNode);
    nextNode->selfNode.preNode = newFreeNode;if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag)) {
        OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode);
        OsMemMergeNode(nextNode);
    }
#ifdef LOSCFG_MEM_HEAD_BACKUP
    else {
        OsMemNodeSave(nextNode);
    }
#endif
⑸  listNodeHead = OS_MEM_HEAD(pool, newFreeNode->selfNode.sizeAndFlag);
    OS_CHECK_NULL_RETURN(listNodeHead);OsMemListAdd(listNodeHead, &newFreeNode->selfNode.freeNodeInfo, firstNode);
#ifdef LOSCFG_MEM_HEAD_BACKUP
    OsMemNodeSave(newFreeNode);
#endif
}

2.1.4 重新申请内存

OsMemReAllocSmaller()函数用于从一个大的内存块里重新申请一个较小的内存,他需要的4个参数分别是:LosMemPoolInfo *pool是内存池起始地址,UINT32 allocSize是重新申请的内存的大小,LosMemDynNode *node是当前需要重新分配内存的内存节点,UINT32 nodeSize是当前节点的大小。⑴设置内存节点selfNode.sizeAndFlag为去除标记后的实际大小,⑵按需分割节点,⑶分割后的节点设置已使用标记,完成完成申请内存。

STATIC INLINE VOID OsMemReAllocSmaller(LosMemPoolInfo *pool, UINT32 allocSize, LosMemDynNode *node, UINT32 nodeSize)
{
    if ((allocSize + OS_MEM_NODE_HEAD_SIZE + OS_MEM_ALIGN_SIZE) <= nodeSize) {
⑴      node->selfNode.sizeAndFlag = nodeSize;OsMemSplitNode(pool, node, allocSize);OS_MEM_NODE_SET_USED_FLAG(node->selfNode.sizeAndFlag);
#ifdef LOSCFG_MEM_HEAD_BACKUP
        OsMemNodeSave(node);
#endif

        OS_MEM_REDUCE_USED(&pool->stat, nodeSize - allocSize, OS_MEM_TASKID_GET(node));
    }
#ifdef LOSCFG_MEM_LEAKCHECK
    OsMemLinkRegisterRecord(node);
#endif
}

2.1.5 合并节点重新申请内存

最后,再来看下函数函数OsMemMergeNodeForReAllocBigger(),用于合并内存节点,重新分配更大的内存空间。它需要5个参数,LosMemPoolInfo *pool是内存池起始地址,UINT32 allocSize是重新申请的内存的大小,LosMemDynNode *node是当前需要重新分配内存的内存节点,UINT32 nodeSize是当前节点的大小,LosMemDynNode *nextNode是下一个内存节点。⑴处获取内存池中第一个内存节点,⑵设置内存节点selfNode.sizeAndFlag为去除标记后的实际大小,⑶把下一个节点从链表上删除,然后合并节点。⑷处如果合并后的节点大小超过需要重新分配的大小,则分割节点。

STATIC INLINE VOID OsMemMergeNodeForReAllocBigger(LosMemPoolInfo *pool, UINT32 allocSize, LosMemDynNode *node,
                                                  UINT32 nodeSize, LosMemDynNode *nextNode)
{const VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE);

⑵  node->selfNode.sizeAndFlag = nodeSize;OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode);
    OsMemMergeNode(nextNode);
    if ((allocSize + OS_MEM_NODE_HEAD_SIZE + OS_MEM_ALIGN_SIZE) <= node->selfNode.sizeAndFlag) {OsMemSplitNode(pool, node, allocSize);
    }

    OS_MEM_ADD_USED(&pool->stat, node->selfNode.sizeAndFlag - nodeSize, OS_MEM_TASKID_GET(node));

    OS_MEM_NODE_SET_USED_FLAG(node->selfNode.sizeAndFlag);
#ifdef LOSCFG_MEM_HEAD_BACKUP
    OsMemNodeSave(node);
#endif
#ifdef LOSCFG_MEM_LEAKCHECK
    OsMemLinkRegisterRecord(node);
#endif
}

2.2 初始化动态内存池

我们分析下初始化动态内存池函数UINT32 LOS_MemInit(VOID *pool, UINT32 size)的代码。我们先看看函数参数,VOID *pool是动态内存池的起始地址,UINT32 size是初始化的动态内存池的总大小,size需要小于等于*pool开始的内存区域的大小,否则会影响后面的内存区域,还需要大于动态内存池的最小值OS_MEM_MIN_POOL_SIZE[pool, pool + size]不能和其他内存池冲突。

我们看下代码,⑴处对传入参数进行校验,传入参数需要内存对齐。⑵处如果开启多内存池的宏LOSCFG_MEM_MUL_POOL才会执行。⑶处调用函数OsMemInit()进行内存池初始化,这是初始化的内存的核心函数。⑷处开启宏LOSCFG_KERNEL_MEM_SLAB_EXTENTION支持时,才会执行。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_MemInit(VOID *pool, UINT32 size)
{
    UINT32 intSave;if ((pool == NULL) || (size < OS_MEM_MIN_POOL_SIZE)) {
        return LOS_NOK;
    }

    if (!IS_ALIGNED(size, OS_MEM_ALIGN_SIZE) || !IS_ALIGNED(pool, OS_MEM_ALIGN_SIZE)) {
        PRINT_WARN("pool [%p, %p) size 0x%x should be aligned with OS_MEM_ALIGN_SIZE\n",
                   pool, (UINTPTR)pool + size, size);
        size = OS_MEM_ALIGN(size, OS_MEM_ALIGN_SIZE) - OS_MEM_ALIGN_SIZE;
    }

    MEM_LOCK(intSave);if (OsMemMulPoolInit(pool, size)) {
        MEM_UNLOCK(intSave);
        return LOS_NOK;
    }if (OsMemInit(pool, size) != LOS_OK) {
        (VOID)OsMemMulPoolDeinit(pool);
        MEM_UNLOCK(intSave);
        return LOS_NOK;
    }OsSlabMemInit(pool, size);
    MEM_UNLOCK(intSave);

    LOS_TRACE(MEM_INFO_REQ, pool);
    return LOS_OK;
}

我们继续看下函数OsMemInit()。⑴处设置动态内存池信息结构体LosMemPoolInfo的起始地址和大小。⑵处初始化第二部分的多双向链表表头结构体LosMultipleDlinkHead。⑶处获取内存池的第一个内存控制节点,然后设置它的大小为(poolSize - (UINT32)((UINTPTR)newNode - (UINTPTR)pool) - OS_MEM_NODE_HEAD_SIZE),该节点大小等于第三部分减去一个内存控制节点的大小。再设置该内存节点的上一个节点为内存池的最后一个节点OS_MEM_END_NODE(pool, poolSize)

⑷处根据第一个内存节点的大小获取多链表表头节点listNodeHead,然后把内存节点插入到双向链表中。⑸处获取内存池的尾节点,清除内容然后设置其大小和上一个节点,并设置已使用标记。⑹处对未节点设置魔术字,指定使用该内存块的任务Id。如果开启调测宏LOSCFG_MEM_TASK_STATLOSCFG_MEM_HEAD_BACKUP,还会有些其他操作,自行阅读即可。

STATIC UINT32 OsMemInit(VOID *pool, UINT32 size)
{
    LosMemPoolInfo *poolInfo = (LosMemPoolInfo *)pool;
    LosMemDynNode *newNode = NULL;
    LosMemDynNode *endNode = NULL;
    LOS_DL_LIST *listNodeHead = NULL;
    UINT32 poolSize = OsLmsMemInit(pool, size);
    if (poolSize == 0) {
        poolSize = size;
    }
⑴  poolInfo->pool = pool;
    poolInfo->poolSize = poolSize;OsDLnkInitMultiHead(OS_MEM_HEAD_ADDR(pool));
⑶  newNode = OS_MEM_FIRST_NODE(pool);
    newNode->selfNode.sizeAndFlag = (poolSize - (UINT32)((UINTPTR)newNode - (UINTPTR)pool) - OS_MEM_NODE_HEAD_SIZE);
    newNode->selfNode.preNode = (LosMemDynNode *)OS_MEM_END_NODE(pool, poolSize);
⑷  listNodeHead = OS_MEM_HEAD(pool, newNode->selfNode.sizeAndFlag);
    if (listNodeHead == NULL) {
        return LOS_NOK;
    }
    LOS_ListTailInsert(listNodeHead, &(newNode->selfNode.freeNodeInfo));

⑸  endNode = (LosMemDynNode *)OS_MEM_END_NODE(pool, poolSize);
    (VOID)memset_s(endNode, sizeof(*endNode), 0, sizeof(*endNode));
    endNode->selfNode.preNode = newNode;
    endNode->selfNode.sizeAndFlag = OS_MEM_NODE_HEAD_SIZE;
    OS_MEM_NODE_SET_USED_FLAG(endNode->selfNode.sizeAndFlag);OsMemSetMagicNumAndTaskID(endNode);
#ifdef LOSCFG_MEM_TASK_STAT
    UINT32 statSize = sizeof(poolInfo->stat);
    (VOID)memset_s(&poolInfo->stat, statSize, 0, statSize);
    poolInfo->stat.memTotalUsed = sizeof(LosMemPoolInfo) + OS_MULTI_DLNK_HEAD_SIZE +
                                  OS_MEM_NODE_GET_SIZE(endNode->selfNode.sizeAndFlag);
    poolInfo->stat.memTotalPeak = poolInfo->stat.memTotalUsed;
#endif

#ifdef LOSCFG_MEM_HEAD_BACKUP
    OsMemNodeSave(newNode);
    OsMemNodeSave(endNode);
#endif

    return LOS_OK;
}

2.3 申请动态内存

初始化动态内存池后,我们可以使用函数VOID *LOS_MemAlloc(VOID *pool, UINT32 size)来申请动态内存,下面分析下源码。

⑴处对参数进行校验,内存池地址不能为空,申请的内存大小不能为0。⑵处判断申请的内存大小是否已标记为使用或对齐。把下一个可用节点赋值给nodeTmp。⑶处如果支持SLAB,则先尝试从SLAB中获取内存,否则执行⑷调用函数OsMemAllocWithCheck(pool, size)申请内存块。

LITE_OS_SEC_TEXT VOID *LOS_MemAlloc(VOID *pool, UINT32 size)
{
    VOID *ptr = NULL;
    UINT32 intSave;if ((pool == NULL) || (size == 0)) {
        return NULL;
    }

    if (g_MALLOC_HOOK != NULL) {
        g_MALLOC_HOOK();
    }

    MEM_LOCK(intSave);
    do {if (OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size)) {
            break;
        }

⑶      ptr = OsSlabMemAlloc(pool, size);
        if (ptr == NULL) {
⑷          ptr = OsMemAllocWithCheck(pool, size);
        }
    } while (0);
#ifdef LOSCFG_MEM_RECORDINFO
    OsMemRecordMalloc(ptr, size);
#endif
    OsLmsSetAfterMalloc(ptr);

    MEM_UNLOCK(intSave);

    LOS_TRACE(MEM_ALLOC, pool, (UINTPTR)ptr, size);
    return ptr;
}

我们继续分析函数OsMemAllocWithCheck(pool, size)。⑴处获取内存池中第一个内存节点,⑵计算出对齐后的内存大小,然后调用函数OsMemFindSuitableFreeBlock()获取适合的内存块,如果找不到适合的内存块,函数返回NULL。⑶处如果找到的内存块大于需要的内存大小,则执行分割操作。⑷处把已分配的内存节点从链表中删除,然后设置魔术字和使用该内存块的任务Id,然后标记该内存块已使用。⑸处如果开启宏LOSCFG_MEM_TASK_STAT,还需要做些记录操作,自行分析即可。⑹处返回内存块的数据区的地址,这个是通过内存控制节点+1定位到数据区内存地址实现的。申请内存完成,调用申请内存的函数中可以使用申请的内存了。

STATIC VOID *OsMemAllocWithCheck(LosMemPoolInfo *pool, UINT32 size)
{
    LosMemDynNode *allocNode = NULL;
    UINT32 allocSize;

#ifdef LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK
    LosMemDynNode *tmpNode = NULL;
    LosMemDynNode *preNode = NULL;
#endifconst VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE);

#ifdef LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK
    if (OsMemIntegrityCheck(pool, &tmpNode, &preNode)) {
        OsMemIntegrityCheckError(tmpNode, preNode);
        return NULL;
    }
#endif

⑵  allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);
    allocNode = OsMemFindSuitableFreeBlock(pool, allocSize);
    if (allocNode == NULL) {
        OsMemInfoAlert(pool, allocSize);
        return NULL;
    }if ((allocSize + OS_MEM_NODE_HEAD_SIZE + OS_MEM_ALIGN_SIZE) <= allocNode->selfNode.sizeAndFlag) {
        OsMemSplitNode(pool, allocNode, allocSize);
    }OsMemListDelete(&allocNode->selfNode.freeNodeInfo, firstNode);
    OsMemSetMagicNumAndTaskID(allocNode);
    OS_MEM_NODE_SET_USED_FLAG(allocNode->selfNode.sizeAndFlag);OS_MEM_ADD_USED(&pool->stat, OS_MEM_NODE_GET_SIZE(allocNode->selfNode.sizeAndFlag),
                    OS_MEM_TASKID_GET(allocNode));
    OsMemNodeDebugOperate(pool, allocNode, size);return (allocNode + 1);
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。