LiteOS内核源码分析:动态内存之Bestfit分配算法

本文分享自华为云社区《LiteOS内核源码分析系列十三 动态内存Bestfit分配算法》,原文作者:zhushy 。

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

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

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

  • 动态内存:在动态内存池中分配用户指定大小的内存块。
    • 优点:按需分配。
    • 缺点:内存池中可能出现碎片。
  • 静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。
    • 优点:分配和释放效率高,静态内存池中无碎片。
    • 缺点:只能申请到初始化预设大小的内存块,不能按需申请。

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

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

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

  • LiteOS内核动态内存源代码

包括动态内存的bestfit算法私有头文件kernel\base\mem\bestfit\los_memory_internal.h、动态内存私有头文件kernel\base\include\los_memory_pri.h、内存头文件kernel\include\los_memory.h、多链表头文件kernel\base\include\los_multipledlinkhead_pri.h、C源代码文件kernel\base\mem\bestfit\los_memory.c、C源代码文件kernel\base\mem\bestfit\los_multipledlinkhead.c。

  • 开发指南文档–内存

在线文档https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E5%86%85%E5%AD%98。

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

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

1.1 动态内存结构体定义

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

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

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字节,则数组的第一个双向链表存储的是所有size为2^min<size< 2^(min+1)的free节点,第二个双向链表存储的是所有size为2^(min+1)<size< 2^(min+2)的free节点,依次类推第n个双向链表存储的是所有size为2^(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_LOG2和OS_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);
#endif
⑶  OsMemClearNode(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_STAT、LOSCFG_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;
#endif
⑴  const 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);
}

2.4 按指定字节对齐申请动态内存

我们还可以使用函数VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary),从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存。该函数需要3个参数,VOID *pool为内存池起始地址,UINT32 size为需要申请的内存大小,UINT32 boundary内存对齐数值。当申请内存后得到的内存地址VOID *ptr,对齐后的内存地址为VOID *alignedPtr,二者的偏移值使用UINT32 gapSize保存。因为已经按OS_MEM_ALIGN_SIZE内存对齐了,最大偏移值为boundary - OS_MEM_ALIGN_SIZE。下面分析下源码。

⑴处对参数进行校验,内存池地址不能为空,申请的内存大小不能为0,对齐字节boundary不能为0,还需要是2的幂。⑵处校验下对齐内存后是否会数据溢出。⑶处计算对齐后需要申请的内存大小,然后判断内存大小数值没有已使用或已对齐标记。⑷处调用函数申请到内存VOID *ptr,然后计算出对齐的内存地址VOID *alignedPtr,如果二者相等则返回。⑸处计算出对齐内存的偏移值,⑹处获取申请到的内存的控制节点,设置已对齐标记。⑺对偏移值设置对齐标记,然后把偏移值保存在内存VOID *alignedPtr的前4个字节里。⑻处重新定向要返回的指针,完成申请对齐的内存。

LITE_OS_SEC_TEXT VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary)
{
    UINT32 useSize;
    UINT32 gapSize;
    VOID *ptr = NULL;
    VOID *alignedPtr = NULL;
    LosMemDynNode *allocNode = NULL;
    UINT32 intSave;

⑴  if ((pool == NULL) || (size == 0) || (boundary == 0) || !IS_POW_TWO(boundary) ||
        !IS_ALIGNED(boundary, sizeof(VOID *))) {
        return NULL;
    }

    MEM_LOCK(intSave);
    do {
⑵      if ((boundary - sizeof(gapSize)) > ((UINT32)(-1) - size)) {
            break;
        }

⑶      useSize = (size + boundary) - sizeof(gapSize);
        if (OS_MEM_NODE_GET_USED_FLAG(useSize) || OS_MEM_NODE_GET_ALIGNED_FLAG(useSize)) {
            break;
        }

⑷      ptr = OsMemAllocWithCheck(pool, useSize);

        alignedPtr = (VOID *)OS_MEM_ALIGN(ptr, boundary);
        if (ptr == alignedPtr) {
            break;
        }

⑸      gapSize = (UINT32)((UINTPTR)alignedPtr - (UINTPTR)ptr);
⑹      allocNode = (LosMemDynNode *)ptr - 1;
        OS_MEM_NODE_SET_ALIGNED_FLAG(allocNode->selfNode.sizeAndFlag);
#ifdef LOSCFG_MEM_RECORDINFO
        allocNode->selfNode.originSize = size;
#endif
#ifdef LOSCFG_MEM_HEAD_BACKUP
        OsMemNodeSaveWithGapSize(allocNode, gapSize);
#endif

⑺      OS_MEM_NODE_SET_ALIGNED_FLAG(gapSize);
        *(UINT32 *)((UINTPTR)alignedPtr - sizeof(gapSize)) = gapSize;

⑻      ptr = alignedPtr;
    } while (0);
#ifdef LOSCFG_MEM_RECORDINFO
    OsMemRecordMalloc(ptr, size);
#endif
    OsLmsSetAfterMalloc(ptr);

    MEM_UNLOCK(intSave);

    LOS_TRACE(MEM_ALLOC_ALIGN, pool, (UINTPTR)ptr, size, boundary);
    return ptr;
}

2.5 释放动态内存

对申请的内存块使用完毕,我们可以使用函数UINT32 LOS_MemFree(VOID *pool, VOID *ptr)来释放动态态内存,需要2个参数,VOID *pool是初始化过的动态内存池地址。VOID *ptr是需要释放的动态内存块的数据区的起始地址,注意这个不是内存控制节点的地址。下面分析下源码。

⑴处对传入的参数先进行校验。⑵如果内存是从SLAB中申请的内存,需要释放到SLAB内存区。⑶处调用函数OsMemFree(pool, ptr)完成内存的释放。

LITE_OS_SEC_TEXT UINT32 LOS_MemFree(VOID *pool, VOID *ptr)
{
    UINT32 ret;
    UINT32 intSave;

⑴  if ((pool == NULL) || (ptr == NULL) ||
        !IS_ALIGNED(pool, sizeof(VOID *)) || !IS_ALIGNED(ptr, sizeof(VOID *))) {
        return LOS_NOK;
    }

    MEM_LOCK(intSave);

⑵  if (OsSlabMemFree(pool, ptr)) {
        ret = LOS_OK;
        goto OUT;
    }

⑶  ret = OsMemFree(pool, ptr);
OUT:
    OsLmsSetAfterFree(ptr);
    MEM_UNLOCK(intSave);

    LOS_TRACE(MEM_FREE, pool, (UINTPTR)ptr);
    return ret;
}

我们继续分析下函数OsMemFree(pool, ptr)。⑴处获取gapSize,对于函数LOS_MemAlloc()申请的内存,gapSize对应控制节点LosMemCtlNode的成员变量sizeAndFlag;对于函数LOS_MemAllocAlign()申请的内存,gapSize对应内存对齐偏移值。对于第一种情况,只标记已使用,第二种情况只标记已对齐。⑵处表示如果既标记已使用,又标记已对齐,则返回错误。⑶处获取内存控制节点,对于第二种情况这样的获取是错误的。⑷处代码校准第二种情况,如果gapSize标记为已对齐,去除gapSize中的对齐标记,获取偏移值。⑸处对偏移值进行校验,然后执行⑹获取获取内存控制节点。⑺处进一步调用函数完成内存的释放。

UINT32 OsMemFree(VOID *pool, VOID *ptr)
{
    UINT32 ret = LOS_NOK;
    UINT32 gapSize;
    LosMemDynNode *node = NULL;

    do {
⑴      gapSize = *(UINT32 *)((UINTPTR)ptr - sizeof(UINT32));
⑵      if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize) && OS_MEM_NODE_GET_USED_FLAG(gapSize)) {
            PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
            goto OUT;
        }

⑶      node = (LosMemDynNode *)((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE);

⑷      if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize)) {
            gapSize = OS_MEM_NODE_GET_ALIGNED_GAPSIZE(gapSize);
⑸          if ((gapSize & (OS_MEM_ALIGN_SIZE - 1)) || (gapSize > ((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE))) {
                PRINT_ERR("illegal gapSize: 0x%x\n", gapSize);
                break;
            }
⑹          node = (LosMemDynNode *)((UINTPTR)ptr - gapSize - OS_MEM_NODE_HEAD_SIZE);
        }
#ifndef LOSCFG_MEM_HEAD_BACKUP
⑺      ret = OsDoMemFree(pool, ptr, node);
#endif
    } while (0);
#ifdef LOSCFG_MEM_HEAD_BACKUP
    ret = OsMemBackupCheckAndRetore(pool, ptr, node);
    if (!ret) {
        ret = OsDoMemFree(pool, ptr, node);
    }
#endif

OUT:
#ifdef LOSCFG_MEM_RECORDINFO
    if (ret == LOS_NOK) {
        OsMemRecordFree(ptr, 0);
    }
#endif
    return ret;
}

我们继续看下函数OsDoMemFree(),该函数进一步调用函数OsMemFreeNode(node, pool)完成内存释放。

LITE_OS_SEC_TEXT STATIC INLINE UINT32 OsDoMemFree(VOID *pool, const VOID *ptr, LosMemDynNode *node)
{
    UINT32 ret = OsMemCheckUsedNode(pool, node);
    if (ret == LOS_OK) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(ptr, node->selfNode.originSize);
#endif
        OsMemFreeNode(node, pool);
    }
    return ret;
}

函数OsMemFreeNode(node, pool)如下,继续分析。⑴处获取动态内存池的第一个内存控制节点,⑵处去除已使用标记。⑶处处理前一个节点不为空,且没有使用的情况。⑷处执行内存节点合并,然后获取下一个节点nextNode,如果下一个节点也是未使用节点,则把下一个节点从链表中删除,并把空闲节点进行合并。⑸处把前一个节点从链表中删除,基于合并后的内存节点大小重新挂载到链表上。

如果上一个节点已使用,无法和上一个节点合并,则执行⑹获取下一个节点。如果下一个节点也是未使用节点,则把下一个节点从链表中删除,并把空闲节点进行合并。⑺根据内存节点大小获取链表节点,然后把释放的内存节点挂载到链表上,完成内存节点的释放。

STATIC INLINE VOID OsMemFreeNode(LosMemDynNode *node, LosMemPoolInfo *pool)
{
    LosMemDynNode *nextNode = NULL;
    LOS_DL_LIST *listNodeHead = NULL;
⑴  const VOID *firstNode = (const VOID *)((UINT8 *)OS_MEM_HEAD_ADDR(pool) + OS_DLNK_HEAD_SIZE);

    OS_MEM_REDUCE_USED(&pool->stat, OS_MEM_NODE_GET_SIZE(node->selfNode.sizeAndFlag), OS_MEM_TASKID_GET(node));
⑵  node->selfNode.sizeAndFlag = OS_MEM_NODE_GET_SIZE(node->selfNode.sizeAndFlag);
#ifdef LOSCFG_MEM_HEAD_BACKUP
    OsMemNodeSave(node);
#endif
#ifdef LOSCFG_MEM_LEAKCHECK
    OsMemLinkRegisterRecord(node);
#endif
⑶  if ((node->selfNode.preNode != NULL) &&
        !OS_MEM_NODE_GET_USED_FLAG(node->selfNode.preNode->selfNode.sizeAndFlag)) {
        LosMemDynNode *preNode = node->selfNode.preNode;
⑷      OsMemMergeNode(node);
        nextNode = OS_MEM_NEXT_NODE(preNode);
        if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag)) {
            OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode);
            OsMemMergeNode(nextNode);
        }

⑸      OsMemListDelete(&(preNode->selfNode.freeNodeInfo), firstNode);
        listNodeHead = OS_MEM_HEAD(pool, preNode->selfNode.sizeAndFlag);
        OS_CHECK_NULL_RETURN(listNodeHead);

        OsMemListAdd(listNodeHead, &preNode->selfNode.freeNodeInfo, firstNode);
    } else {
⑹      nextNode = OS_MEM_NEXT_NODE(node);
        if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag)) {
            OsMemListDelete(&nextNode->selfNode.freeNodeInfo, firstNode);
            OsMemMergeNode(nextNode);
        }

⑺      listNodeHead = OS_MEM_HEAD(pool, node->selfNode.sizeAndFlag);
        OS_CHECK_NULL_RETURN(listNodeHead);

        OsMemListAdd(listNodeHead, &node->selfNode.freeNodeInfo, firstNode);
    }
}

2.6 重新申请动态内存

我们还可以使用函数VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size),按size大小重新分配内存块,并将原内存块内容拷贝到新内存块。如果新内存块申请成功,则释放原内存块。该函数需要3个参数,VOID *pool为内存池起始地址,VOID *ptr为之前申请的内存地址,UINT32 size为重新申请的内存大小。下面分析下源码。

⑴处对参数进行校验,内存池地址不能为空,内存大小不能含有已使用、已对齐标记。⑵处如果传入的内存地址为空,则等价于LOS_MemAlloc()函数。⑶如果传入size为0,等价于函数LOS_MemFree()。⑷如果开启支持SLAB,需要调用OsMemReallocSlab()函数重新申请SLAB内存。⑸处调用函数OsMemRealloc()进行重新申请内存。

LITE_OS_SEC_TEXT_MINOR VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size)
{
    UINT32 intSave;
    VOID *newPtr = NULL;
    BOOL isSlabMem = FALSE;

⑴  if (OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size) || (pool == NULL)) {
        return NULL;
    }

⑵  if (ptr == NULL) {
        newPtr = LOS_MemAlloc(pool, size);
        goto OUT;
    }

⑶  if (size == 0) {
        (VOID)LOS_MemFree(pool, ptr);
        goto OUT;
    }

    MEM_LOCK(intSave);

⑷  newPtr = OsMemReallocSlab(pool, ptr, &isSlabMem, size);
    if (isSlabMem == TRUE) {
        goto OUT_UNLOCK;
    }

⑸  newPtr = OsMemRealloc(pool, ptr, size);

OUT_UNLOCK:
    MEM_UNLOCK(intSave);
OUT:

    LOS_TRACE(MEM_REALLOC, pool, (UINTPTR)ptr, size);
    return newPtr;
}

进一步看下函数OsMemRealloc()。⑴处获取内存对齐后的大小allocSize,⑵处获取内存对齐之前的地址,稍后会分析该函数OsGetRealPtr()。⑶获取内存控制节点node,然后获取节点的大小nodeSize。⑷处理重新申请的内存小于等于现有的内存的情况,需要调用函数OsMemReAllocSmaller()进行分割,分割完毕返回(VOID *)ptr即可。如果重新申请更大的内存,则执行⑸获取下一个节点,然后⑹处理下一个节点可用且两个节点大小之和大于等于重新申请内存的大小allocSize。执行⑺处的函数,合并节点重新分配内存。

如果连续的节点的大小不满足重新申请内存的大小,则执行⑻处函数重新申请内存。然后执行⑼把之前内存的数据复制到新申请的内存区域,复制失败的话,则把新申请的内存释放掉,并返回退出函数。如果复制成功,继续执行⑽释放掉之前的节点。

STATIC VOID *OsMemRealloc(VOID *pool, VOID *ptr, UINT32 size)
{
    LosMemDynNode *node = NULL;
    LosMemDynNode *nextNode = NULL;
    VOID *tmpPtr = NULL;
    VOID *realPtr = NULL;
    UINT32 nodeSize;
⑴  UINT32 allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);
#ifdef LOSCFG_MEM_RECORDINFO
    const VOID *originPtr = ptr;
#endif

⑵  realPtr = OsGetRealPtr(pool, ptr);
    if (realPtr == NULL) {
        return NULL;
    }

⑶   node = (LosMemDynNode *)((UINTPTR)realPtr - OS_MEM_NODE_HEAD_SIZE);
    if (OsMemCheckUsedNode(pool, node) != LOS_OK) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(originPtr, 0);
#endif
        return NULL;
    }

    nodeSize = OS_MEM_NODE_GET_SIZE(node->selfNode.sizeAndFlag);
⑷  if (nodeSize >= allocSize) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(originPtr, node->selfNode.originSize);
#endif
        OsMemReAllocSmaller(pool, allocSize, node, nodeSize);
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemReallocNodeRecord(node, size, ptr);
#endif
        return (VOID *)ptr;
    }

⑸  nextNode = OS_MEM_NEXT_NODE(node);
⑹  if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->selfNode.sizeAndFlag) &&
        ((nextNode->selfNode.sizeAndFlag + nodeSize) >= allocSize)) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(originPtr, node->selfNode.originSize);
#endif
⑺      OsMemMergeNodeForReAllocBigger(pool, allocSize, node, nodeSize, nextNode);
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemReallocNodeRecord(node, size, ptr);
#endif
        return (VOID *)ptr;
    }

⑻  tmpPtr = OsMemAllocWithCheck(pool, size);
    if (tmpPtr != NULL) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordMalloc(tmpPtr, size);
#endif
        UINT32 gapSize = (UINT32)((UINTPTR)ptr - (UINTPTR)realPtr);
⑼      if (memcpy_s(tmpPtr, size, ptr, (nodeSize - OS_MEM_NODE_HEAD_SIZE - gapSize)) != EOK) {
            (VOID)OsMemFree((VOID *)pool, (VOID *)tmpPtr);
            return NULL;
        }
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(originPtr, node->selfNode.originSize);
#endif
⑽      OsMemFreeNode(node, pool);
    }
    return tmpPtr;
}

我们回过头来,继续看下函数OsGetRealPtr()。⑴获取内存对齐的偏移值,⑵如果偏移值同时标记为已使用和已对齐,则返回错误。⑶如果偏移值标记为已对齐,则执行⑷去除对齐标记,获取单纯的偏移值。然后执行⑸,获取内存对齐之前的数据区内存地址。

STATIC VOID *OsGetRealPtr(const VOID *pool, VOID *ptr)
{
    VOID *realPtr = ptr;
⑴  UINT32 gapSize = *((UINT32 *)((UINTPTR)ptr - sizeof(UINT32)));

⑵  if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize) && OS_MEM_NODE_GET_USED_FLAG(gapSize)) {
#ifdef LOSCFG_MEM_RECORDINFO
        OsMemRecordFree(ptr, 0);
#endif
        PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
        return NULL;
    }
⑶  if (OS_MEM_NODE_GET_ALIGNED_FLAG(gapSize)) {
⑷      gapSize = OS_MEM_NODE_GET_ALIGNED_GAPSIZE(gapSize);
        if ((gapSize & (OS_MEM_ALIGN_SIZE - 1)) ||
            (gapSize > ((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE - (UINTPTR)pool))) {
            PRINT_ERR("[%s:%d]gapSize:0x%x error\n", __FUNCTION__, __LINE__, gapSize);
#ifdef LOSCFG_MEM_RECORDINFO
            OsMemRecordFree(ptr, 0);
#endif
            return NULL;
        }
⑸      realPtr = (VOID *)((UINTPTR)ptr - (UINTPTR)gapSize);
    }
    return realPtr;
}

小结

本文带领大家一起剖析了LiteOS动态内存模块bestfit算法的源代码,包含动态内存的结构体、动态内存池初始化、动态内存申请、释放等。感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/LiteOS/LiteOS/issues 。为了更容易找到LiteOS代码仓,建议访问 https://gitee.com/LiteOS/LiteOS ,关注Watch、点赞Star、并Fork到自己账户下,谢谢。

 

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

相关推荐

鸿蒙案例上新啦 | 购物、听歌、健身:15个模板带你一键激活社交娱乐

页面层级如何组织、跨页共享状态如何同步、列表与详情及个人中心如何形成稳定闭环。鸿蒙开发实践中心将这些高频消费与互动场景沉淀为可参考的标准模板,提供从业务架构到界面实现的快捷实践入口。更多实战案例欢迎前往鸿蒙开发实践中心案例中心查看并开启云端体验。

华为云官方博客 345

鸿蒙案例上新啦 | 出门打车、到家上课:25个模板带你一屏打通日常生活

如何合理拆分页面组件、如何建立单一可信的状态源、如何让用户在每一步操作中都获得明确反馈。鸿蒙开发实践中心以标准化的应用与元服务模板为入口,将清晰的业务架构与可复用的代码结构直观呈现。不妨立即挑选一个感兴趣的方向进入云端环境,从页面布局、组件封装、状态管理到路由组织,亲手跑通你的鸿蒙实战项目。

华为云官方博客 331

LakeAgent:DWS数据仓库的第二大脑,告别 “翻译式运维”,迈入智能自治时代

和普通 SQL 改写工具不同,LakeAgent 自动做多方案代价对比,主动规避负优化陷阱:补充分区键过滤实现剪枝,扫描分区由 37 收敛至 1,执行代价从 758.28 降至 113.74;未来的数据仓库,将不止能算、能存,更能看懂自己、诊断自己、理解自己。长久以来,数仓的智能都依附于人:信号需要人解读,瓶颈需要人研判,经验困在个体里,无法随底座永续生长。CPU 高、CPU 告警、CPU 诊断、CPU 使用率过高、CPU 满。语法、错误码、计费、性能调优、最佳实践、故障处置等全场景内容。

华为云官方博客 559

如何使用RDS Supabase后端BaaS能力:文件存储(File Storage)

• product-images(公开桶,允许公共读取):策略名称"Public read for product-images",允许SELECT,目标角色anon,策略定义bucket_id = 'product-images'。上传/删除需另配INSERT/DELETE策略,详见SQL。• product-specs(私有桶,仅认证用户访问自己的文件夹):建议拆为上传、读取、删除三条策略,策略定义均为 bucket_id = 'product-specs' 且仅限本人文件夹(详见下文 SQL)。

华为云官方博客 480

如何使用RDS Supabase后端BaaS能力:实时订阅与推送(Realtime)

Realtime 把数据库的每次变更同步到客户端:表里增、删、改一行,订阅者无需刷新页面或主动请求即可秒级收到。三大能力各司其职:• Postgres Changes — 监听表的 INSERT/UPDATE/DELETE,变化即通知• Broadcast — 客户端之间直接互发,适合瞬时通知、协同编辑• Presence — 跟踪频道里“谁在线”典型场景有实时数据展示(订单、库存、监控指标即时刷新)、即时通讯(消息收发、在线名单、“对方正在输入”)、协同编辑与实时通知。

华为云官方博客 470

AI Agent如何通过MCP连接到华为云RDS Supabase应用平台

Schema元数据列出所有可访问的表(按schema分组)。Schema元数据列出表的列及元数据(类型/默认值/约束)。Schema元数据列出所有约束(PK/FK/UNIQUE/CHECK/EXCLUDE)。可按schema/表/类型过滤。Schema元数据列出所有外键关系。可按schema/表过滤。Schema元数据列出所有索引及定义和大小。可按schema/表过滤。Schema元数据列出已应用的数据库迁移记录。Schema元数据列出已安装的PostgreSQL扩展。

华为云官方博客 477

华为大数据Flink让每一 CU 算力,跑出开源 5.74 倍的性能

1. 开源Apache Flink 已成为业界流计算事实标准,但在极端重负载场景下存在性能瓶颈。2. 华为大数据Flink通过多项自研深度优化,在执行计划、算子执行、状态存储三个层面构建协同的优化体系。3. Nexmark Benchmark 测试结果:整体性能提升 5.74 倍,极端重负载场景性能领先最高 18.3 倍。4. 100% 兼容 Apache Flink 1.20,业务代码零改造,无缝迁移。

华为云官方博客 382

LakeAgent实战 | 业务分析篇从一句提问到业务分析

本文以“订单金额变化”分析为例,展示LakeAgent如何通过自然语言提问,结合DataArtsStudio的数据资产与专业技能,帮助业务人员快速定位数据、生成SQL、追踪血缘、生成报告。从问题出发,逐步实现数据发现、变化洞察、原因追溯与团队协作,最终形成可复用的分析流程与企业规则,显著提升数据分析效率与一致性。

华为云官方博客 392

华为云RDS Supabase AI应用已开放公测 :30分钟搭建AI应用后端

认证、存储、实时推送、数据库API——4个后端模块,传统开发需要数周。今天,你只需要30分钟。AI创业公司的全栈开发工程师。上周老板说"下周要上线的AI对话应用,后端你来搞"——认证鉴权、数据库接口、文件存储、实时推送,每一个模块都需要数周搭建,而市场不会等你。小张打开华为云RDS Supabase,30分钟搭好认证、数据库API、对象存储,剩下的时间全用来打磨AI交互体验。基于开源Supabase+华为云RDS PostgreSQL全托管能力,为开发者提供。

华为云官方博客 399

如何使用RDS Supabase后端BaaS能力:数据库函数(Database Function)

上一篇介绍了 Database Data API——建表即自动生成增删改查接口,解决单表读写,但聚合统计、跨表事务、字段自动维护等逻辑已超出其能力边界。本篇推进到数据库函数(Database Function):把业务逻辑封装进数据库,前端通过 .rpc() 调用,由触发器自动响应数据变更。上一篇中,小张基于 Data API 完成了产品目录的建表与基础读写。但新需求随之而来:按分类统计产品数与价格区间、上架新品同时写产品表与定价表、产品修改后自动刷新更新时间。

华为云官方博客 465

如何使用RDS Supabase后端BaaS能力:创表即生成API(Database Data API)

认证、存储、实时推送、数据库API——4个后端模块,传统开发要数周。本系列逐个拆解,首篇聚焦数据库API(Database Data API):建表即得 API,无需手写后端。小张,AI创业公司的全栈工程师。老板让他做个产品目录页,展示产品与价格、支持上下架。传统做法要搭后端、写接口、配权限,工期少说1-2周;而在华为云RDS Supabase,建两张表、应用 SDK 直连,几分钟增删改查全跑通。

华为云官方博客 479

从一句话到一个能出去的链接,AI开发者空间做了什么

自行搭建的 Agent、正在使用的开源项目、团队既有的工具,都可以接入,可以通过AI创建或者手动搭建环境;华为云 AI开发者空间——面向开发者的云端一站式开发平台,做的就是把这段路接上:环境免安装,浏览器打开即为完整的 Linux 运行环境,预置的 AI 智能体开箱即用。第一个任务建议选能出结果的:做一个游戏、生成一个页面、创建一台实例,从需求走到可访问的链接,一次就能看清这个环境在做什么。沙箱是一个完整的 Linux 环境,依赖、服务、端口都是真实的,不是模拟。环境释放后,权限随之失效。

华为云官方博客 426

华为云码道配置自定义大模型,四种方式完整实操

案例介绍。

华为云官方博客 628

图数据库:实现穿透式监管的数据基座

穿透式监管标志着国企监管从“管企业”向“管资本”转型过程中的关键一跃。而图数据库正是这一跃的技术支点。从股权穿透到资金链穿透,从供应链穿透到数据穿透,华为GES图数据库正在将“穿透式监管”从理念变为可落地、可操作的实践。无论是天眼查式的全量链路查询,还是某集团财经的税务规则判断,抑或是芯片供应链式的部件风险分析,乃至通过Palantir构建决策知识图谱,其底层逻辑都是一致的——用图的力量,穿透一切形式的“隔离层”,直抵风险本质与责任核心。

华为云官方博客 665

《公共云三十问》正式收官,奔赴企业AI百场景新征程

今年6月底,公共云产业联盟重点推出《公共云三十问》专栏,以每周三问的节奏,连载十周,解读《先进公共云》白皮书,分享国计民生系列案例,旨在凝聚共识、推动落地实践。同时,专栏分析公共云在产业升级、民生福祉、科技创新、全球发展等方面的作用,梳理中国特色公共云发展路径:以普惠云拓展用云广度,以智算云提升用云深度,以自主云夯实用云厚度,以全球云打开用云跨度。这十问从专属云、行业云切入,深入政务、金融、交通、电力、钢铁、化工、油气、制造等国计民生场景,回答公共云“怎么用”。

华为云官方博客 473

开发者小技巧|IAM配置,让码道代码智能体连续30天免登录

案例介绍。

华为云官方博客 646

码道深度对接CodeArts,让需求自动落地为代码

案例介绍。

华为云官方博客 720

【公共云三十问 之三十】拥抱AI,企业如何建设面向智能体的专属云?

企业可分阶段建设覆盖开发、运行、运营及安全的一体化智能体平台——从构建一站式、高低码协同的智能体开发平台,到打造高性能、极致弹性的运行时环境,再到搭建AgentOps驱动的持续运营与运维体系,同时构建端到端、多层次的全链路安全防护体系。在数据湖建设方面,遵循“存量复用、平滑演进、AI导向、分步迭代”原则,分三阶段推进——打造AI多模数据湖核心能力、完成存算分离与统一元数据管理、实现闭环赋能全面支撑智能体规模化落地,在保护既有投资的前提下将传统数据平台转型为AI数据专属供给平台。

华为云官方博客 823

【公共云三十问 之二十九】国计民生之制造:制造云如何重塑数智时代的新质生产力?

平台打通软件交付的完整路径,覆盖需求管理、代码托管、代码检查、编译构建、制品仓库、部署、测试等关键环节,支持大模型能力融入研发全流程,实现从需求到交付的端到端管理。通过构建“研发一朵云”,方案打破地域与组织壁垒,实现跨团队协同开发,有效缩短软件交付周期,助力工业制造企业从“传统研发”走向“敏捷研发”,加速数智化转型进程。当制造云真正成为数据流通的平台、AI创新的底座,公共云的社会价值将从基础设施层向生产场景层持续延伸,为新型工业化建设和制造强国战略注入源源不断的动力,重塑数智时代的中国制造新质生产力。

华为云官方博客 789

告别重复手工迁仓,码道MCP+Skill实现仓库批量迁移

案例介绍。

华为云官方博客 790

【公共云三十问 之二十八】国计民生之油气:智算云如何叩响数智时代的“地心蓝金”?

西南油气田以“向地球深部要资源”为战略方向,联合以华为云Stack为代表的公共云服务商共同打造统一算力底座,建设西南区域数据湖与通用技术平台,构建“勘探开发工程技术、天然气生产过程管控、天然气产运储销”等三个一体化协同应用场景,支撑油气勘探、开发生产、管输储运等全业务链的数智化转型。当智算云真正成为数据流通的平台、AI创新的底座,油气田得以将算力效能、数据价值与智能技术深度融入勘探开发全链条,将“看不见的算力”转化为“摸得着的储量”,以智能化手段持续降低勘探风险、缩短发现周期、提高采收效率,确保生产安全。

华为云官方博客 613

【公共云三十问 之二十七】国计民生之化工:数智云如何重塑传统化工新范式?

在架构方面,方案构建云边协同的统一体系,涵盖AI协同、数据协同与运营协同三大维度——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现“边用边学”的智能闭环,模型平均精度提升20%以上。此外,方案提供一站式企业级工业物联网平台、数据湖与数据治理平台,覆盖工业数据分析“采、存、算、管、用”全流程,打通生产现场与管理系统间的数据壁垒,有效消除数据孤岛。层面,方案充分发挥软硬协同优势,实现计算、存储、网络与云平台的深度协同,训练线性度超95%,为大模型训练推理提供强劲算力支撑。▲化工“数智”新模式。

华为云官方博客 663

【公共云三十问 之二十六】国计民生之钢铁:数智云如何淬炼中国钢铁新力量?

同时,构建云边端协同的统一架构,支撑集团总部与各生产基地两级协同发展——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现训推一体化的资源调度与高效协同,为钢铁行业的数智化转型构建了坚实的算力底座。层面,方案构建覆盖“工业数采→边缘IT/OT融合→统一数据湖→数据治理平台→数据应用”全链路的一站式数据治理与流通平台,打通生产现场与管理系统间的数据壁垒,让数据“采得到、流得动、用得好”,加速数据价值从生产现场向经营决策层流动释放。湘潭钢铁集团的钢铁数智云,是钢铁工业智能云建设的标杆案例。

华为云官方博客 526

LakeWatch——MRS 智能运维Agent

从资源池、队列、作业等多个维度,对集群CPU与内存资源进行精细化分析与对比,直观呈现不同时间段的空闲、满载等使用状态,识别资源浪费,指导治理优化,为扩缩容决策提供数据支撑。对HDFS存储进行多维度深度分析(目录、用户、用户组等),涵盖使用量、增量、文件大小分布及数据热度等指标,帮助快速识别增长较快的目录、长期未访问的文件,统计目录文件数量与小文件占比,评估HDFS总存储量与扩缩容需求,为存储治理与资源规划提供全面数据支撑。Agent主动巡检,识别异常趋势并提前预警,让运维从"事后处理"转向"事前预防"。

华为云官方博客 515

华为云专题文档发布:丰富的AI案例、选型指导和概念解读

发布 “云服务选型”、“云计算概念”专题,提供云服务选型指导,助您选择匹配业务的云服务,解读云计算相关概念,开启云上学习之路。开发者工具和文档专区上线,提供从学习、实践到反馈的全链路支持。帮助中心新增100余篇AI案例。

华为云官方博客 286

一个人,也能用AI做点“星东西”

华为云星耀创想者大赛-2026华为云OPC创意大赛等你来~

华为云官方博客 290

从需求到可执行工作项:MCP+Skill 让智能体规划真正落地

回顾整个体验,以前让智能体帮我拆需求,它是洋洋洒洒给我列了一大篇 Markdown,看着挺像那么回事,但最后还是得我一条条往 CodeArts Req 里搬,搬完再手动挂父子关系、排日期,拆需求五分钟,搬运两小时。现在好了,MCP 给智能体接上了"手",Skill 给它装上了"项目经理的脑子",一句话下去,34 个工作项连带排期、层级关系全给我建好了,甘特图一拉,齐刷刷都在两周内,节省了多少工作量啊,美滋滋。本次实操在码道代码智能体基础上,构建了一套"需求→可执行工作项"的自动化规划方案。

华为云官方博客 576

华为云码道「码力续航计划」更新!

✅上新GLM‑5.3‑Flash,高速响应,代码生成、工程解析、Agent调试高效输出。新增GLM‑5.3‑Flash福利模型,每日解锁1000万免费Tokens。● 本次活动共发放总量价值100万元Tokens,发完即止!把耗算力的编码重活,交给华为云码道代码智能体🚀🚀。● 每日额度准时刷新,当日清零、次日满血重置。✅复杂算法、大项目解析、批量代码生成随便跑。✅不用抠额度、不用算消耗,放开调试大胆迭代。✅无需额外操作,登录即送,直接切换模型使用。● 把算力交给码道,让撸码不再内耗👏🏻。

华为云官方博客 687

【公共云三十问 之二十四】国计民生之交通:交通云如何铺就数智通衢新坦途?

交通行业作为支撑实体经济的关键基础设施,涵盖公路、铁路、机场、港口、地铁等众多子领域,其中,公路网络作为综合交通运输体系的基础骨架,承担着全国超过70%的客货运量,其数字化、智能化转型的需求尤为迫切。方面,提供Agent开发框架、知识库管理、RAG工程、意图识别、静态应用编排及模型评测等能力,支持客户将行业知识、业务流程与AI能力深度融合,快速构建面向收费稽核、路网调度、应急处置等场景的智能应用。开发并实现落地应用,全面覆盖建设、管理、养护、运营、服务、安全、办公七大业务领域。

华为云官方博客 463

【公共云三十问 之二十五】国计民生之电力:电力云如何点亮数智电力新引擎?

方案构建了覆盖数据获取、质量评估到标准化处理的全链路能力。基于公共云服务商智算底座及气象大模型,通过时空图神经网络重新建模产流机制,中国华电建成超50TB多源融合数据集,突破时空建模与机理融合等五大关键技术,短期径流预测精度较传统机理模型提升5个百分点以上,实现15天中期径流预测精度超85%,推理效率达秒级。基于气象大模型的基础架构与预训练能力,客户可直接在大模型之上开展增量训练与微调,充分利用场站所在区域的高精度历史数据(如3公里、1公里分辨率),针对不同场站和区域的地形地貌与气候特征进行模型调优。

华为云官方博客 381
上一篇: 破解class文件的第一步:深入理解JAVA Class文件
下一篇: 4种语义分割数据集Cityscapes上SOTA方法总结
华为云开发者联盟
华为云开发者联盟 企业官方账号 企业官方账号
博客等级 码龄10年 72万+粉丝 5985原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值