从结构体内存池初始化到申请释放,详细解读鸿蒙轻内核的动态内存管理

Posted 华为云开发者社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从结构体内存池初始化到申请释放,详细解读鸿蒙轻内核的动态内存管理相关的知识,希望对你有一定的参考价值。

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

 

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

 

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

 

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

 

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

优点:按需分配。

缺点:内存池中可能出现碎片。

 

  • 静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。

优点:分配和释放效率高,静态内存池中无碎片。

缺点:只能申请到初始化预设大小的内存块,不能按需申请。

 

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

 

OpenHarmony LiteOS-M 动态内存在 TLSF 算法的基础上,对区间的划分进行了优化,获得更优的性能,降低了碎片率。动态内存核心算法框图如下:

根据空闲内存块的大小,使用多个空闲链表来管理。根据内存空闲块大小分为两个部分:[4, 127]和[27, 231],如上图 size class 所示:

 

  • 对[4,127]区间的内存进行等分,如上图绿色部分所示,分为 31 个小区间,每个小区间对应内存块大小为 4 字节的倍数。每个小区间对应一个空闲内存链表和用于标记对应空闲内存链表是否为空的一个比特位,值为 1 时,空闲链表非空。[4,127]区间的内存使用 1 个 32 位无符号整数位图标记。

 

  • 大于 127 字节的空闲内存块,按照 2 的次幂区间大小进行空闲链表管理。总共分为 24 个小区间,每个小区间又等分为 8 个二级小区间,见上图蓝色的 Size Class 和 Size SubClass 部分。每个二级小区间对应一个空闲链表和用于标记对应空闲内存链表是否为空的一个比特位。总共 24*8=192 个二级小区间,对应 192 个空闲链表和 192/32=6 个 32 位无符号整数位图标记。

 

例如,当有 40 字节的空闲内存需要插入空闲链表时,对应小区间[40,43],第 10 个空闲链表,位图标记的第 10 比特位。把 40 字节的空闲内存挂载第 10 个空闲链表上,并判断是否需要更新位图标记。当需要申请 40 字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。当有 580 字节的空闲内存需要插入空闲链表时,对应二级小区间[2^9,2^9+2^6],第 31+2*8=47 个空闲链表,第 2 个位图标记的第 17 比特位。把 580 字节的空闲内存挂载第 47 个空闲链表上,并判断是否需要更新位图标记。当需要申请 580 字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。如果对应的空闲链表为空,则向更大的内存区间去查询是否有满足条件的空闲链表,实际计算时,会一次性查找到满足申请大小的空闲链表。

 

动态内存管理结构如下图所示:

  • 内存池池头部分

内存池池头部分包含内存池信息和位图标记数组和空闲链表数组。内存池信息包含内存池起始地址及堆区域总大小,内存池属性。位图标记数组有 7 个 32 位无符号整数组成,每个比特位标记对应的空闲链表是否挂载空闲内存块节点。空闲内存链表包含 223 个空闲内存头节点信息,每个空闲内存头节点信息维护内存节点头和空闲链表中的前驱、后继空闲内存节点。

 

  • 内存池节点部分

包含 3 种类型节点,未使用空闲内存节点,已使用内存节点,尾节点。每个内存节点维护一个前序指针,指向内存池中上一个内存节点,维护大小和使用标记,标记该内存节点的大小和是否使用等。空闲内存节点和已使用内存节点后面的数据域,尾节点没有数据域。

 

本文通过分析动态内存模块的源码,帮助读者掌握动态内存的使用。本文中所涉及的源码,以 OpenHarmonyLiteOS-M 内核为例,均可以在开源站点
gitee.com/openharmony… 获取。接下来,我们看下动态内存的结构体,动态内存初始化,动态内存常用操作的源代码。

 

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

1.1 动态内存结构体定义

动态内存的结构体有动态内存池信息结构体 OsMemPoolInfo,动态内存池头结构体 OsMemPoolHead、动态内存节点头结构体 OsMemNodeHead,已使用内存节点结构体 OsMemUsedNodeHead,空闲内存节点结构体 OsMemFreeNodeHead。这些结构体定义在文件 kernel\\src\\mm\\los_memory.c 中,下文会结合上文的动态内存管理结构示意图对各个结构体的成员变量进行说明。

1.1.1 动态内存池池头相关结构体

动态内存池信息结构体 OsMemPoolInfo 维护内存池的开始地址和大小信息。三个主要的成员是内存池开始地址.pool,内存池大小.poolSize 和内存值属性.attr。如果开启宏 LOSCFG_MEM_WATERLINE,还会维护内存池的水线数值。

struct OsMemPoolInfo {
    VOID *pool;               /* 内存池的内存开始地址 */
    UINT32 totalSize;         /* 内存池总大小 */
    UINT32 attr;              /* 内存池属性 */
#if (LOSCFG_MEM_WATERLINE == 1)
    UINT32 waterLine;         /* 内存池中内存最大使用值 */
    UINT32 curUsedSize;       /* 内存池中当前已使用的大小 */
#endif
};

动态内存池头结构体 OsMemPoolHead 源码如下,除了动态内存池信息结构体 structOsMemPoolInfo info,还维护 2 个数组,一个是空闲内存链表位图数组 freeListBitmap[],一个是空闲内存链表数组 freeList[]。宏定义 OS_MEM_BITMAP_WORDS 和 OS_MEM_FREE_LIST_COUNT 后文会介绍。

struct OsMemPoolHead {
    struct OsMemPoolInfo info;
    UINT32 freeListBitmap[OS_MEM_BITMAP_WORDS];
    struct OsMemFreeNodeHead *freeList[OS_MEM_FREE_LIST_COUNT];
#if (LOSCFG_MEM_MUL_POOL == 1)
    VOID *nextPool;
#endif
};

1.1.2 动态内存池内存节点相关结构体

先看下动态内存节点头结构体 OsMemNodeHead 的定义,⑴处如果开启内存节点完整性检查的宏LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK,会维护魔术字.magic 进行校验。⑵处如果开启内存泄漏检查的宏,会维护链接寄存器数组 linkReg[]。⑶处的成员变量是个指针组合体,内存池中的每个内存节点头维护指针执行上一个内存节点。⑷处维护内存节点的大小和标记信息。

struct OsMemNodeHead {
  #if (LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK == 1)
⑴    UINT32 magic;
  #endif
  #if (LOSCFG_MEM_LEAKCHECK == 1)
⑵    UINTPTR linkReg[LOSCFG_MEM_RECORD_LR_CNT];
  #endif
      union {
          struct OsMemNodeHead *prev; /* The prev is used for current node points to the previous node */
          struct OsMemNodeHead *next; /* The next is used for sentinel node points to the expand node */
⑶     } ptr;
 #if (LOSCFG_MEM_FREE_BY_TASKID == 1)
⑷    UINT32 taskID : 6;
      UINT32 sizeAndFlag : 26;
  #else
      UINT32 sizeAndFlag;
  #endif
  };

接着看下已使用内存节点结构体 OsMemUsedNodeHead,该结构体比较简单,直接以动态内存节点头结构体 OsMemNodeHead 作为唯一的成员。

struct OsMemUsedNodeHead {
    struct OsMemNodeHead header;
};

我们再看下空闲内存节点结构体 OsMemFreeNodeHead,除了动态内存节点头结构体 OsMemNodeHead 成员,还包含 2 个指针分别指向上一个和下一个空闲内存节点。

struct OsMemFreeNodeHead {
    struct OsMemNodeHead header;
    struct OsMemFreeNodeHead *prev;
    struct OsMemFreeNodeHead *next;
};

1.2 动态内存核心算法相关的宏和函数

动态内存中还提供了一些和 TLSF 算法相关的宏定义和内联函数,这些宏非常重要,在分析源代码前需要熟悉下这些宏的定义。可以结合上文的动态内存核心算法框图进行学习。⑴处的宏对处于[2^n,2^(n+1)],其中(n=7,8,…30)区间的大内存块进行 2^3=8 等分。⑵处的宏,定义处于[4,127]区间的小内存块划分为 31 个,即 4,8,12,…,124。⑶处定义小内存的上界值,考虑内存对齐和粒度,最大值只能取到 124。

 

⑷处的宏表示处于[2^n,2^(n+1)],其中(n=7,8,…30)区间的大内存分为 24 个小区间,其中 n=7 就是⑺处定义的宏 OS_MEM_LARGE_START_BUCKET。⑻处对应空闲内存链表的长度。⑼处是空闲链表位图数组的长度,31 个小内存使用 1 个位图字,所以需要加 1。⑽处定义位图掩码,每个位图字是 32 位无符号整数。继续看下内联函数。⑾处函数查找位图字中的第一个 1 的比特位,这个实现的功能类似内建函数__builtin_ctz。该函数用于获取空闲内存链表对应的位图字中,第一个挂载着空闲内存块的空闲内存链表。⑿处获取位图字中的最后一个 1 的比特位,(从 32 位二进制数值从左到右依次第 0,1,…,31 位)。⒀处函数名称中的 Log 是对数英文 logarithm 的缩写,函数用于计算以 2 为底的对数的整数部分。⒁处获取内存区间的大小级别编号,对于小于 128 字节的,有 31 个级别,对处于[2^n,2^(n+1)],其中(n=7,8,…30)区间的内存,有 24 个级别。⒂处根据内存大小,内存区间一级编号获取获取二级小区间的编号,对处于[2^n,2^(n+1)],其中(n=7,8,…30)区间的内存,有 8 个二级小区间。

 /* The following is the macro definition and interface implementation related to the TLSF. */


    /* Supposing a Second Level Index: SLI = 3. */
⑴  #define OS_MEM_SLI                      3
    /* Giving 1 free list for each small bucket: 4, 8, 12, up to 124. */
⑵  #define OS_MEM_SMALL_BUCKET_COUNT       31
⑶  #define OS_MEM_SMALL_BUCKET_MAX_SIZE    128
    /* Giving 2^OS_MEM_SLI free lists for each large bucket. */
⑷  #define OS_MEM_LARGE_BUCKET_COUNT       24
    /* OS_MEM_SMALL_BUCKET_MAX_SIZE to the power of 2 is 7. */
⑺  #define OS_MEM_LARGE_START_BUCKET       7


    /* The count of free list. */
⑻  #define OS_MEM_FREE_LIST_COUNT  (OS_MEM_SMALL_BUCKET_COUNT + (OS_MEM_LARGE_BUCKET_COUNT << OS_MEM_SLI))
    /* The bitmap is used to indicate whether the free list is empty, 1: not empty, 0: empty. */
⑼  #define OS_MEM_BITMAP_WORDS     ((OS_MEM_FREE_LIST_COUNT >> 5) + 1)


⑽  #define OS_MEM_BITMAP_MASK 0x1FU


    /* Used to find the first bit of 1 in bitmap. */
⑾  STATIC INLINE UINT16 OsMemFFS(UINT32 bitmap)
    {
        bitmap &= ~bitmap + 1;
        return (OS_MEM_BITMAP_MASK - CLZ(bitmap));
    }


    /* Used to find the last bit of 1 in bitmap. */
⑿  STATIC INLINE UINT16 OsMemFLS(UINT32 bitmap)
    {
        return (OS_MEM_BITMAP_MASK - CLZ(bitmap));
    }


⒀  STATIC INLINE UINT32 OsMemLog2(UINT32 size)
    {
        return (size > 0) ? OsMemFLS(size) : 0;
    }


    /* Get the first level: f = log2(size). */
⒁  STATIC INLINE UINT32 OsMemFlGet(UINT32 size)
    {
        if (size < OS_MEM_SMALL_BUCKET_MAX_SIZE) {
            return ((size >> 2) - 1); /* 2: The small bucket setup is 4. */
        }
        return (OsMemLog2(size) - OS_MEM_LARGE_START_BUCKET + OS_MEM_SMALL_BUCKET_COUNT);
    }


    /* Get the second level: s = (size - 2^f) * 2^SLI / 2^f. */
⒂  STATIC INLINE UINT32 OsMemSlGet(UINT32 size, UINT32 fl)
    {
        if ((fl < OS_MEM_SMALL_BUCKET_COUNT) || (size < OS_MEM_SMALL_BUCKET_MAX_SIZE)) {
            PRINT_ERR("fl or size is too small, fl = %u, size = %u\\n", fl, size);
            return 0;
        }


        UINT32 sl = (size << OS_MEM_SLI) >> (fl - OS_MEM_SMALL_BUCKET_COUNT + OS_MEM_LARGE_START_BUCKET);
        return (sl - (1 << OS_MEM_SLI));
    }

2、动态内存常用操作

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

2.1 动态内存内部接口

2.1.1 设置和清理空闲内存链表标记位

⑴处函数 OsMemSetFreeListBit 需要 2 个参数,一个是内存池池头 head,一个是空闲内存链表索引 index。当空闲内存链表上挂载有空闲内存块时,位图字相应的位需要设置为 1。⑴处函数 OsMemClearFreeListBit 做相反的操作,当空闲内存链表上不再挂载空闲内存块时,需要对应的比特位清零。

 STATIC INLINE VOID OsMemSetFreeListBit(struct OsMemPoolHead *head, UINT32 index)
  {
⑴    head->freeListBitmap[index >> 5] |= 1U << (index & 0x1f);
  }


  STATIC INLINE VOID OsMemClearFreeListBit(struct OsMemPoolHead *head, UINT32 index)
  {
⑵    head->freeListBitmap[index >> 5] &= ~(1U << (index & 0x1f));
  }

2.1.2 合并内存节点

函数 VOIDOsMemMergeNode(struct OsMemNodeHead *node)用于合并给定节点 structOsMemNodeHead *node 和它前一个空闲节点。⑴处把前一个节点的大小加上要合入节点的大小。⑵处获取给定节点的下一个节点,然后执行⑶把它的前一个节点指向给定节点的前一个节点,完成节点的合并。其中宏OS_MEM_NODE_GET_LAST_FLAG 用于判断是否最后一个节点,默认为 0,可以自行查看下该宏的定义。

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


⑴  node->ptr.prev->sizeAndFlag += node->sizeAndFlag;
⑵  nextNode = (struct OsMemNodeHead *)((UINTPTR)node + node->sizeAndFlag);
    if (!OS_MEM_NODE_GET_LAST_FLAG(nextNode->sizeAndFlag)) {
⑶      nextNode->ptr.prev = node->ptr.prev;
    }
}
复制代码

2.1.3 分割内存节点

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

 

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

STATIC INLINE VOID OsMemSplitNode(VOID *pool, struct OsMemNodeHead *allocNode, UINT32 allocSize)
{
    struct OsMemFreeNodeHead *newFreeNode = NULL;
    struct OsMemNodeHead *nextNode = NULL;


⑴  newFreeNode = (struct OsMemFreeNodeHead *)(VOID *)((UINT8 *)allocNode + allocSize);
    newFreeNode->header.ptr.prev = allocNode;
    newFreeNode->header.sizeAndFlag = allocNode->sizeAndFlag - allocSize;
⑵  allocNode->sizeAndFlag = allocSize;
⑶  nextNode = OS_MEM_NEXT_NODE(&newFreeNode->header);
    if (!OS_MEM_NODE_GET_LAST_FLAG(nextNode->sizeAndFlag)) {
⑷      nextNode->ptr.prev = &newFreeNode->header;
        if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->sizeAndFlag)) {
⑸          OsMemFreeNodeDelete(pool, (struct OsMemFreeNodeHead *)nextNode);
            OsMemMergeNode(nextNode);
        }
    }


⑹  OsMemFreeNodeAdd(pool, newFreeNode);
}

2.1.4 重新申请内存

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

STATIC INLINE VOID OsMemReAllocSmaller(VOID *pool, UINT32 allocSize, struct OsMemNodeHead *node, UINT32 nodeSize)
{
#if (LOSCFG_MEM_WATERLINE == 1)
    struct OsMemPoolHead *poolInfo = (struct OsMemPoolHead *)pool;
#endif
⑴  node->sizeAndFlag = nodeSize;
    if ((allocSize + OS_MEM_MIN_LEFT_SIZE) <= nodeSize) {
⑵       OsMemSplitNode(pool, node, allocSize);
#if (LOSCFG_MEM_WATERLINE == 1)
        poolInfo->info.curUsedSize -= nodeSize - allocSize;
#endif
    }
⑶  OS_MEM_NODE_SET_USED_FLAG(node->sizeAndFlag);
#if (LOSCFG_MEM_LEAKCHECK == 1)
    OsMemLinkRegisterRecord(node);
#endif
}

2.1.5 合并节点重新申请内存

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

STATIC INLINE VOID OsMemMergeNodeForReAllocBigger(VOID *pool, UINT32 allocSize, struct OsMemNodeHead *node,
                                                  UINT32 nodeSize, struct OsMemNodeHead *nextNode)
{
⑴  node->sizeAndFlag = nodeSize;
⑵  OsMemFreeNodeDelete(pool, (struct OsMemFreeNodeHead *)nextNode);
    OsMemMergeNode(nextNode);
    if ((allocSize + OS_MEM_MIN_LEFT_SIZE) <= node->sizeAndFlag) {
⑶       OsMemSplitNode(pool, node, allocSize);
    }
⑷  OS_MEM_NODE_SET_USED_FLAG(node->sizeAndFlag);
    OsMemWaterUsedRecord((struct OsMemPoolHead *)pool, node->sizeAndFlag - nodeSize);
#if (LOSCFG_MEM_LEAKCHECK == 1)
    OsMemLinkRegisterRecord(node);
#endif
}
复制代码

2.1.6 空闲内存链表相关操作

动态内存提供了针对空闲内存链表的几个操作,我们依次分析下这些操作的代码。首先看下函数OsMemFreeListIndexGet,根据内存节点大小获取空闲内存链表的索引。⑴处先获取一级索引,⑵处获取二级索引,然后计算空闲链表的索引并返回。

STATIC INLINE UINT32 OsMemFreeListIndexGet(UINT32 size)
{
⑴  UINT32 fl = OsMemFlGet(size);
    if (fl < OS_MEM_SMALL_BUCKET_COUNT) {
        return fl;
    }


⑵  UINT32 sl = OsMemSlGet(size, fl);
    return (OS_MEM_SMALL_BUCKET_COUNT + ((fl - OS_MEM_SMALL_BUCKET_COUNT) << OS_MEM_SLI) + sl);
}

接着看下函数 OsMemListAdd,如何把空闲内存节点插入空闲内存链表。⑴处获取空闲链表的第一个节点,如果节点不为空,则把这个节点的前驱节点设置为待插入节点 node。⑵处设置待插入节点的前驱、后继节点,然后把该节点赋值给空闲链表 pool->freeList[listIndex]。最后执行⑶处代码,把设置空闲链表位图字,并设置魔术字。

STATIC INLINE VOID OsMemListAdd(struct OsMemPoolHead *pool, UINT32 listIndex, struct OsMemFreeNodeHead *node)
{
⑴  struct OsMemFreeNodeHead *firstNode = pool->freeList[listIndex];
    if (firstNode != NULL) {
        firstNode->prev = node;
    }
⑵  node->prev = NULL;
    node->next = firstNode;
    pool->freeList[listIndex] = node;
⑶  OsMemSetFreeListBit(pool, listIndex);
    OS_MEM_SET_MAGIC(&node->header);
}

最后,分析下函数 OsMemListDelete 如何从空闲内存链表删除指定的空闲内存节点。⑴处如果删除的节点是空闲内存链表的第一个节点,则需要把空闲链表执行待删除节点的下一个节点。如果下一个节点为空,需要执行⑵清除空闲链表的位图字。否则执行⑶把下一个节点的前驱节点设置为空。如果待删除节点不是空闲链表的第一个节点,执行⑷把待删除节点的前驱节点的后续节点设置为待删除节点的后继节点。如果待删除节点不为最后一个节点,需要执行⑸把待删除节点的后继节点的前驱节点设置为待删除节点的前驱节点。最后需要设置下魔术字。

STATIC INLINE VOID OsMemListDelete(struct OsMemPoolHead *pool, UINT32 listIndex, struct OsMemFreeNodeHead *node)
{
⑴  if (node == pool->freeList[listIndex]) {
        pool->freeList[listIndex] = node->next;
        if (node->next == NULL) {
⑵          OsMemClearFreeListBit(pool, listIndex);
        } else {
⑶          node->next->prev = NULL;
        }
    } else {
⑷      node->prev->next = node->next;
        if (node->next != NULL) {
⑸          node->next->prev = node->prev;
        }
    }
    OS_MEM_SET_MAGIC(&node->header);
}

2.1.7 空闲内存节点相关操作

动态内存提供了针对空闲内存的几个操作,如 OsMemFreeNodeAdd、OsMemFreeNodeDelete、OsMemFreeNodeGet。函数 OsMemFreeNodeAdd 用于把一个空闲内存节点加入相应的空闲内存链表上。⑴处调用函数获取空闲内存链表的索引,然后执行⑵把空闲内存节点加入空闲链表。

STATIC INLINE VOID OsMemFreeNodeAdd(VOID *pool, struct OsMemFreeNodeHead *node)
{
⑴  UINT32 index = OsMemFreeListIndexGet(node->header.sizeAndFlag);
    if (index >= OS_MEM_FREE_LIST_COUNT) {
        LOS_Panic("The index of free lists is error, index = %u\\n", index);
    }
⑵  OsMemListAdd(pool, index, node);
}

函数 OsMemFreeNodeDelete 用于把一个空闲内存节点从相应的空闲内存链表上删除。代码较简单,获取空闲内存链表的索引,然后调用函数 OsMemListDelete 进行删除。

STATIC INLINE VOID OsMemFreeNodeDelete(VOID *pool, struct OsMemFreeNodeHead *node)
{
    UINT32 index = OsMemFreeListIndexGet(node->header.sizeAndFlag);
    OsMemListDelete(pool, index, node);
}

函数 OsMemFreeNodeGet 根据内存池地址和需要的内存大小获取满足大小条件的空闲内存块。⑴处调用函数获取满足大小条件的内存块,然后执行⑵把获取到的内存块从空闲内存链表删除,返回内存节点地址。

STATIC INLINE struct OsMemNodeHead *OsMemFreeNodeGet(VOID *pool, UINT32 size)
{
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    UINT32 index;
⑴  struct OsMemFreeNodeHead *firstNode = OsMemFindNextSuitableBlock(pool, size, &index);
    if (firstNode == NULL) {
        return NULL;
    }


⑵  OsMemListDelete(poolHead, index, firstNode);


    return &firstNode->header;
}

最后,分析下函数OsMemFindNextSuitableBlock。⑴处根据需要的内存块大小获取一级区间编号,如果申请的内存处于[4,127]区间,执行⑵处记录空闲内存链表索引。如果需要申请的是大内存,执行⑶处代码。先获取二级区间索引,然后计算出空闲内存链表的索引值 index。这样计算出来的空闲内存链表下可能并没有挂载空闲内存块,调用⑷处函数 OsMemNotEmptyIndexGet 获取挂载空闲内存块的空闲内存链表索引值。如果成功获取到满足大小的空闲内存块,返回空闲链表索引值,否则继续执行后续代码。⑹处对空闲链表位图字进行遍历,循环中的自增变量 index 对应一级区间编号。如果位图字不为空,执行⑺获取这个位图字对应的最大的空闲内存链表的索引。如果执行到⑻处,说明没有匹配到合适的内存块,返回空指针。⑼处表示存在满足大小的空闲内存链表,调用函数OsMemFindCurSuitableBlock 获取合适的内存块并返回。⑽处标签表示获取到合适的空闲内存链表索引,返回空闲内存链表。

STATIC INLINE struct OsMemFreeNodeHead *OsMemFindNextSuitableBlock(VOID *pool, UINT32 size, UINT32 *outIndex)
{
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
⑴  UINT32 fl = OsMemFlGet(size);
    UINT32 sl;
    UINT32 index, tmp;
    UINT32 curIndex = OS_MEM_FREE_LIST_COUNT;
    UINT32 mask;


    do {
        if (fl < OS_MEM_SMALL_BUCKET_COUNT) {
⑵          index = fl;
        } else {
⑶          sl = OsMemSlGet(size, fl);
            curIndex = ((fl - OS_MEM_SMALL_BUCKET_COUNT) << OS_MEM_SLI) + sl + OS_MEM_SMALL_BUCKET_COUNT;
            index = curIndex + 1;
        }


⑷      tmp = OsMemNotEmptyIndexGet(poolHead, index);
        if (tmp != OS_MEM_FREE_LIST_COUNT) {
⑸          index = tmp;
            goto DONE;
        }


⑹      for (index = LOS_Align(index + 1, 32); index < OS_MEM_FREE_LIST_COUNT; index += 32) {
            mask = poolHead->freeListBitmap[index >> 5]; /* 5: Divide by 32 to calculate the index of the bitmap array. */
            if (mask != 0) {
⑺              index = OsMemFFS(mask) + index;
                goto DONE;
            }
        }
    } while (0);


⑻  if (curIndex == OS_MEM_FREE_LIST_COUNT) {
        return NULL;
    }


⑼  *outIndex = curIndex;
    return OsMemFindCurSuitableBlock(poolHead, curIndex, size);
DONE:
    *outIndex = index;
⑽  return poolHead->freeList[index];
}

我们再详细分析下函数 OsMemNotEmptyIndexGet 的源码。⑴处根据空闲内存链表索引获取位图字,⑵处判断空闲内存链表索引对应的一级内存区间对应的二级小内存区间是否存在满足条件的空闲内存块。其中 index& OS_MEM_BITMAP_MASK 对索引只取低 5 位后,可以把索引值和位图字中的比特位关联起来,比如 index 为 39 时,index& OS_MEM_BITMAP_MASK 等于 7,对应位图字的第 7 位。表达式~((1<< (index & OS_MEM_BITMAP_MASK)) - 1)则用于表示大于空闲内存链表索引 index 的索引值对应的位图字。⑵处的语句执行后,mask 就表示空闲链表索引值大于 index 的链表索引对应的位图字的值。当 mask 不为 0 时,表示存在满足内存大小的空闲内存块,则执行⑶处代码,其中 OsMemFFS(mask)获取位图字中第一个为 1 的比特位位数,该位对应着挂载空闲内存块的链表。(index& ~OS_MEM_BITMAP_MASK)对应链表索引的高位,加上位图字位数就计算出挂载着满足申请条件的空闲内存链表的索引值。

STATIC INLINE UINT32 OsMemNotEmptyIndexGet(struct OsMemPoolHead *poolHead, UINT32 index)
{
⑴  UINT32 mask = poolHead->freeListBitmap[index >> 5]; /* 5: Divide by 32 to calculate the index of the bitmap array. */
⑵  mask &= ~((1 << (index & OS_MEM_BITMAP_MASK)) - 1);
    if (mask != 0) {
⑶      index = OsMemFFS(mask) + (index & ~OS_MEM_BITMAP_MASK);
        return index;
    }


    return OS_MEM_FREE_LIST_COUNT;
}

最后,再看下函数 OsMemFindCurSuitableBlock。⑴处循环遍历空闲内存链表上挂载的内存块,如果遍历到的内存块大小大于需要的大小,则执行⑵返回该空闲内存块。否则返回空指针。

STATIC INLINE struct OsMemFreeNodeHead *OsMemFindCurSuitableBlock(struct OsMemPoolHead *poolHead,
                                        UINT32 index, UINT32 size)
{
    struct OsMemFreeNodeHead *node = NULL;


⑴  for (node = poolHead->freeList[index]; node != NULL; node = node->next) {
        if (node->header.sizeAndFlag >= size) {
⑵           return node;
        }
    }


    return NULL;
}

2.2 初始化动态内存池

我们分析下初始化动态内存池函数 UINT32LOS_MemInit(VOID *pool, UINT32 size)的代码。我们先看看函数参数,VOID *pool 是动态内存池的起始地址,UINT32size 是初始化的动态内存池的总大小,size 需要小于等于*pool 开始的内存区域的大小,否则会影响后面的内存区域,还需要大于动态内存池的最小值 OS_MEM_MIN_POOL_SIZE。[pool,pool + size]不能和其他内存池冲突。我们看下代码,⑴处对传入参数进行校验,⑵处对传入参数进行是否内存对齐校验,如果没有内存对齐会返回错误码。⑶处调用函数 OsMemPoolInit()进行内存池初始化,这是初始化内存的核心函数。⑷处开启宏 LOSCFG_MEM_MUL_POOL 多内存池支持时,才会执行。

UINT32 LOS_MemInit(VOID *pool, UINT32 size)
{
⑴  if ((pool == NULL) || (size <= OS_MEM_MIN_POOL_SIZE)) {
        return OS_ERROR;
    }


⑵  if (((UINTPTR)pool & (OS_MEM_ALIGN_SIZE - 1)) || \\
        (size & (OS_MEM_ALIGN_SIZE - 1))) {
        PRINT_ERR("LiteOS heap memory address or size configured not aligned:address:0x%x,size:0x%x, alignsize:%d\\n", \\
                  (UINTPTR)pool, size, OS_MEM_ALIGN_SIZE);
        return OS_ERROR;
    }


⑶  if (OsMemPoolInit(pool, size)) {
        return OS_ERROR;
    }


#if (LOSCFG_MEM_MUL_POOL == 1)
⑷  if (OsMemPoolAdd(pool, size)) {
        (VOID)OsMemPoolDeinit(pool);
        return OS_ERROR;
    }
#endif


#if OS_MEM_TRACE
    LOS_TraceReg(LOS_TRACE_MEM_TIME, OsMemTimeTrace, LOS_TRACE_MEM_TIME_NAME, LOS_TRACE_ENABLE);
    LOS_TraceReg(LOS_TRACE_MEM_INFO, OsMemInfoTrace, LOS_TRACE_MEM_INFO_NAME, LOS_TRACE_ENABLE);
#endif


    OsHookCall(LOS_HOOK_TYPE_MEM_INIT, pool, size);


    return LOS_OK;
}

我们继续看下函数 OsMemPoolInit()。⑴处设置动态内存池信息结构体 structOsMemPoolHead *poolHead 的起始地址和大小,⑵处设置内存池属性设置为锁定、不可扩展。⑶处获取内存池的第一个内存控制节点,然后设置它的大小,该节点大小等于内存池总大小减去内存池池头大小和一个内存节点头大小。然后再设置该内存节点的上一个节点为内存池的最后一个节点 OS_MEM_END_NODE(pool,size)。

 

⑷处调用宏给节点设置魔术字,然后把内存节点插入到空闲内存链表中。⑸处获取内存池的尾节点,设置魔术字,然后执行⑹设置尾节点大小为 0 和设置上一个节点,并设置已使用标记。如果开启调测宏 LOSCFG_MEM_WATERLINE,还会有些其他操作,自行阅读即可。

STATIC UINT32 OsMemPoolInit(VOID *pool, UINT32 size)
{
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    struct OsMemNodeHead *newNode = NULL;
    struct OsMemNodeHead *endNode = NULL;


    (VOID)memset_s(poolHead, sizeof(struct OsMemPoolHead), 0, sizeof(struct OsMemPoolHead));


⑴  poolHead->info.pool = pool;
    poolHead->info.totalSize = size;
    poolHead->info.attr &= ~(OS_MEM_POOL_UNLOCK_ENABLE | OS_MEM_POOL_EXPAND_ENABLE); /* default attr: lock, not expand. */


⑶  newNode = OS_MEM_FIRST_NODE(pool);
    newNode->sizeAndFlag = (size - sizeof(struct OsMemPoolHead) - OS_MEM_NODE_HEAD_SIZE);
    newNode->ptr.prev = OS_MEM_END_NODE(pool, size);
⑷  OS_MEM_SET_MAGIC(newNode);
    OsMemFreeNodeAdd(pool, (struct OsMemFreeNodeHead *)newNode);


    /* The last mem node */
⑸  endNode = OS_MEM_END_NODE(pool, size);
    OS_MEM_SET_MAGIC(endNode);
#if OS_MEM_EXPAND_ENABLE
    endNode->ptr.next = NULL;
    OsMemSentinelNodeSet(endNode, NULL, 0);
#else
⑹  endNode->sizeAndFlag = 0;
    endNode->ptr.prev = newNode;
    OS_MEM_NODE_SET_USED_FLAG(endNode->sizeAndFlag);
#endif
#if (LOSCFG_MEM_WATERLINE == 1)
    poolHead->info.curUsedSize = sizeof(struct OsMemPoolHead) + OS_MEM_NODE_HEAD_SIZE;
    poolHead->info.waterLine = poolHead->info.curUsedSize;
#endif


    return LOS_OK;
}

2.3 申请动态内存

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

 

⑴处对参数进行校验,内存池地址不能为空,申请的内存大小不能为 0。⑵处判断申请的内存大小是否已标记为使用或内存对齐。⑶处调用函数 OsMemAlloc(poolHead,size, intSave)申请内存块。

VOID *LOS_MemAlloc(VOID *pool, UINT32 size)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endif


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


    if (size < OS_MEM_MIN_ALLOC_SIZE) {
        size = OS_MEM_MIN_ALLOC_SIZE;
    }


    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    VOID *ptr = NULL;
    UINT32 intSave;


    MEM_LOCK(poolHead, intSave);
    do {
⑵      if (OS_MEM_NODE_GET_USED_FLAG(size) || OS_MEM_NODE_GET_ALIGNED_FLAG(size)) {
            break;
        }
⑶      ptr = OsMemAlloc(poolHead, size, intSave);
    } while (0);
    MEM_UNLOCK(poolHead, intSave);


#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_MALLOC, timeUsed);


    LOS_MEM_POOL_STATUS poolStatus = {0};
    (VOID)LOS_MemInfoGet(pool, &poolStatus);
    UINT8 fragment = 100 - poolStatus.maxFreeNodeSize * 100 / poolStatus.totalFreeSize; /* 100: percent denominator. */
    UINT8 usage = LOS_MemTotalUsedGet(pool) * 100 / LOS_MemPoolSizeGet(pool); /* 100: percent denominator. */
    LOS_Trace(LOS_TRACE_MEM_INFO, (UINTPTR)pool & MEM_POOL_ADDR_MASK, fragment, usage, poolStatus.totalFreeSize,
              poolStatus.maxFreeNodeSize, poolStatus.usedNodeNum, poolStatus.freeNodeNum);
#endif


    OsHookCall(LOS_HOOK_TYPE_MEM_ALLOC, pool, size);


    return ptr;
}

我们继续分析函数 OsMemAlloc()。⑴处对申请内存大小加上头结点大小的和进行内存对齐,⑵处从空闲内存链表中获取一个满足申请大小的空闲内存块,如果申请失败,则打印错误信息。⑶处如果找到的内存块大于需要的内存大小,则执行分割操作。⑷处把已分配的内存节点标记为已使用,更新水线记录。⑸返回内存块的数据区的地址,这个是通过内存节点地址加 1 定位到数据区内存地址实现的。申请内存完成,调用申请内存的函数中可以使用申请的内存了。

STATIC INLINE VOID *OsMemAlloc(struct OsMemPoolHead *pool, UINT32 size, UINT32 intSave)
{
    struct OsMemNodeHead *allocNode = NULL;


#if (LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK == 1)
    if (OsMemAllocCheck(pool, intSave) == LOS_NOK) {
        return NULL;
    }
#endif


⑴  UINT32 allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);


#if OS_MEM_EXPAND_ENABLE
retry:
#endif
⑵  allocNode = OsMemFreeNodeGet(pool, allocSize);
    if (allocNode == NULL) {
#if OS_MEM_EXPAND_ENABLE
        if (pool->info.attr & OS_MEM_POOL_EXPAND_ENABLE) {
            INT32 ret = OsMemPoolExpand(pool, allocSize, intSave);
            if (ret == 0) {
                goto retry;
            }
        }
#endif
        PRINT_ERR("---------------------------------------------------"
                  "--------------------------------------------------------\\n");
        MEM_UNLOCK(pool, intSave);
        OsMemInfoPrint(pool);
        MEM_LOCK(pool, intSave);
        PRINT_ERR("[%s] No suitable free block, require free node size: 0x%x\\n", __FUNCTION__, allocSize);
        PRINT_ERR("----------------------------------------------------"
                  "-------------------------------------------------------\\n");
        return NULL;
    }


⑶  if ((allocSize + OS_MEM_MIN_LEFT_SIZE) <= allocNode->sizeAndFlag) {
        OsMemSplitNode(pool, allocNode, allocSize);
    }


⑷  OS_MEM_NODE_SET_USED_FLAG(allocNode->sizeAndFlag);
    OsMemWaterUsedRecord(pool, OS_MEM_NODE_GET_SIZE(allocNode->sizeAndFlag));


#if (LOSCFG_MEM_LEAKCHECK == 1)
    OsMemLinkRegisterRecord(allocNode);
#endif
⑸  return OsMemCreateUsedNode((VOID *)allocNode);
}

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

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

 

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

VOID *LOS_MemAllocAlign(VOID *pool, UINT32 size, UINT32 boundary)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endif


    UINT32 gapSize;


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


    if (size < OS_MEM_MIN_ALLOC_SIZE) {
        size = OS_MEM_MIN_ALLOC_SIZE;
    }


⑵  if ((boundary - sizeof(gapSize)) > ((UINT32)(-1) - size)) {
        return NULL;
    }


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


    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    UINT32 intSave;
    VOID *ptr = NULL;
    VOID *alignedPtr = NULL;


    MEM_LOCK(poolHead, intSave);
    do {
⑷      ptr = OsMemAlloc(pool, useSize, intSave);
        alignedPtr = (VOID *)OS_MEM_ALIGN(ptr, boundary);
        if (ptr == alignedPtr) {
            break;
        }


        /* store gapSize in address (ptr - 4), it will be checked while free */
⑸      gapSize = (UINT32)((UINTPTR)alignedPtr - (UINTPTR)ptr);
⑹      struct OsMemUsedNodeHead *allocNode = (struct OsMemUsedNodeHead *)ptr - 1;
        OS_MEM_NODE_SET_ALIGNED_FLAG(allocNode->header.sizeAndFlag);
⑺      OS_MEM_SET_GAPSIZE_ALIGNED_FLAG(gapSize);
        *(UINT32 *)((UINTPTR)alignedPtr - sizeof(gapSize)) = gapSize;
⑻      ptr = alignedPtr;
    } while (0);
    MEM_UNLOCK(poolHead, intSave);


#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_MEMALIGN, timeUsed);
#endif


    OsHookCall(LOS_HOOK_TYPE_MEM_ALLOCALIGN, pool, size, boundary);


    return ptr;
}

2.5 释放动态内存

对申请的内存块使用完毕,我们可以使用函数 UINT32LOS_MemFree(VOID *pool, VOID *ptr)来释放动态态内存,需要 2 个参数,VOID*pool 是初始化过的动态内存池地址。VOID*ptr 是需要释放的动态内存块的数据区的起始地址,注意这个不是内存控制节点的地址。下面分析下源码,⑴处对传入的参数先进行校验。⑵处获取校准内存对齐后的真实的内存地址,然后获取内存节点头地址。⑶处调用函数 OsMemFree(pool,ptr)完成内存的释放。

UINT32 LOS_MemFree(VOID *pool, VOID *ptr)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endif


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


    UINT32 ret = LOS_NOK;
    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    struct OsMemNodeHead *node = NULL;
    UINT32 intSave;


    MEM_LOCK(poolHead, intSave);
    do {
⑵      ptr = OsGetRealPtr(pool, ptr);
        if (ptr == NULL) {
            break;
        }
        node = (struct OsMemNodeHead *)((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE);
⑶      ret = OsMemFree(poolHead, node);
    } while (0);
    MEM_UNLOCK(poolHead, intSave);


#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_FREE, timeUsed);
#endif


    OsHookCall(LOS_HOOK_TYPE_MEM_FREE, pool, ptr);


    return ret;
}

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

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


⑵  if (OS_MEM_GAPSIZE_CHECK(gapSize)) {
        PRINT_ERR("[%s:%d]gapSize:0x%x error\\n", __FUNCTION__, __LINE__, gapSize);
        return NULL;
    }


⑶  if (OS_MEM_GET_GAPSIZE_ALIGNED_FLAG(gapSize)) {
⑷      gapSize = OS_MEM_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);
            return NULL;
        }
⑸      realPtr = (VOID *)((UINTPTR)ptr - (UINTPTR)gapSize);
    }
    return realPtr;
}

2.6 重新申请动态内存

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

 

⑴处对参数进行校验,内存池地址不能为空,内存大小不能含有已使用、已对齐标记。⑵处如果传入的内存地址为空,则等价于 LOS_MemAlloc()函数。⑶如果传入 size 为 0,等价于函数 LOS_MemFree()。⑷处保证申请的内存块大小至少为系统允许的最小值 OS_MEM_MIN_ALLOC_SIZE。⑸处获取内存对齐之前的内存地址,上文已分析该函数 OsGetRealPtr()。⑹处由数据域内存地址计算出内存控制节点 node 的内存地址,然后执行⑺处函数重新申请内存。

VOID *LOS_MemRealloc(VOID *pool, VOID *ptr, UINT32 size)
{
#if OS_MEM_TRACE
    UINT64 start = HalClockGetCycles();
#endif


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


    OsHookCall(LOS_HOOK_TYPE_MEM_REALLOC, pool, ptr, size);


⑵  if (ptr == NULL) {
        return LOS_MemAlloc(pool, size);
    }


⑶  if (size == 0) {
        (VOID)LOS_MemFree(pool, ptr);
        return NULL;
    }


⑷  if (size < OS_MEM_MIN_ALLOC_SIZE) {
        size = OS_MEM_MIN_ALLOC_SIZE;
    }


    struct OsMemPoolHead *poolHead = (struct OsMemPoolHead *)pool;
    struct OsMemNodeHead *node = NULL;
    VOID *newPtr = NULL;
    UINT32 intSave;


    MEM_LOCK(poolHead, intSave);
    do {
⑸      ptr = OsGetRealPtr(pool, ptr);
        if (ptr == NULL) {
            break;
        }


⑹      node = (struct OsMemNodeHead *)((UINTPTR)ptr - OS_MEM_NODE_HEAD_SIZE);
        if (OsMemCheckUsedNode(pool, node) != LOS_OK) {
            break;
        }


⑺      newPtr = OsMemRealloc(pool, ptr, node, size, intSave);
    } while (0);
    MEM_UNLOCK(poolHead, intSave);


#if OS_MEM_TRACE
    UINT64 end = HalClockGetCycles();
    UINT32 timeUsed = MEM_TRACE_CYCLE_TO_US(end - start);
    LOS_Trace(LOS_TRACE_MEM_TIME, (UINTPTR)pool & MEM_POOL_ADDR_MASK, MEM_TRACE_REALLOC, timeUsed);
#endif


    return newPtr;
}

继续分析下函数 OsMemRealloc。⑴处处理重新申请的内存小于等于现有的内存的情况,需要调用函数 OsMemReAllocSmaller()进行分割,分割完毕返回(VOID*)ptr 即可。如果重新申请更大的内存,则执行⑵处代码获取下一个节点,然后执行⑶处理下一个节点可用且两个节点大小之和大于等于重新申请内存的大小 allocSize。执行⑷处的函数,合并节点重新分配内存。如果连续的节点的大小不满足重新申请内存的大小,则执行⑸处函数重新申请内存。申请成功后,执行⑹把之前内存的数据复制到新申请的内存区域,复制失败的话,则把新申请的内存释放掉,并返回 NULL,退出函数。如果复制成功,继续执行⑺释放掉之前的节点。

STATIC INLINE VOID *OsMemRealloc(struct OsMemPoolHead *pool, const VOID *ptr,
                struct OsMemNodeHead *node, UINT32 size, UINT32 intSave)
{
    struct OsMemNodeHead *nextNode = NULL;
    UINT32 allocSize = OS_MEM_ALIGN(size + OS_MEM_NODE_HEAD_SIZE, OS_MEM_ALIGN_SIZE);
    UINT32 nodeSize = OS_MEM_NODE_GET_SIZE(node->sizeAndFlag);
    VOID *tmpPtr = NULL;


⑴  if (nodeSize >= allocSize) {
        OsMemReAllocSmaller(pool, allocSize, node, nodeSize);
        return (VOID *)ptr;
    }


⑵  nextNode = OS_MEM_NEXT_NODE(node);
⑶  if (!OS_MEM_NODE_GET_USED_FLAG(nextNode->sizeAndFlag) &&
        ((nextNode->sizeAndFlag + nodeSize) >= allocSize)) {
⑷      OsMemMergeNodeForReAllocBigger(pool, allocSize, node, nodeSize, nextNode);
        return (VOID *)ptr;
    }


⑸  tmpPtr = OsMemAlloc(pool, size, intSave);
    if (tmpPtr != NULL) {
⑹      if (memcpy_s(tmpPtr, size, ptr, (nodeSize - OS_MEM_NODE_HEAD_SIZE)) != EOK) {
            MEM_UNLOCK(pool, intSave);
            (VOID)LOS_MemFree((VOID *)pool, (VOID *)tmpPtr);
            MEM_LOCK(pool, intSave);
            return NULL;
        }
⑺      (VOID)OsMemFree(pool, node);
    }
    return tmpPtr;

小结

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

 

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



以上是关于从结构体内存池初始化到申请释放,详细解读鸿蒙轻内核的动态内存管理的主要内容,如果未能解决你的问题,请参考以下文章

从五大结构体,带你掌握鸿蒙轻内核动态内存Dynamic Memory

万字解读鸿蒙轻内核物理内存模块

掌握鸿蒙轻内核静态内存的使用,从源码分析开始

掌握鸿蒙轻内核静态内存的使用,从源码分析开始

鸿蒙轻内核源码分析:掌握信号量使用差异

鸿蒙轻内核源码分析:掌握信号量使用差异