关于harmonyos:从五大结构体带你掌握鸿蒙轻内核动态内存Dynamic-Memory

11次阅读

共计 27203 个字符,预计需要花费 69 分钟才能阅读完成。

摘要:本文率领大家一起分析了鸿蒙轻内核的动静内存模块的源代码,蕴含动态内存的构造体、动态内存池初始化、动态内存申请、开释等。

本文分享自华为云社区《鸿蒙轻内核 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 种类型节点,未应用闲暇内存节点,已应用内存节点,尾节点。每个内存节点保护一个前序指针,指向内存池中上一个内存节点,保护大小和应用标记,标记该内存节点的大小和是否应用等。闲暇内存节点和已应用内存节点前面的数据域,尾节点没有数据域。

本文通过剖析动静内存模块的源码,帮忙读者把握动态内存的应用。本文中所波及的源码,以 OpenHarmony LiteOS- M 内核为例,均能够在开源站点 https://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 源码如下,除了动态内存池信息结构体 struct OsMemPoolInfo 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 合并内存节点

函数 VOID OsMemMergeNode(struct OsMemNodeHead node)用于合并给定节点 struct OsMemNodeHead 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 是内存池起始地址,struct OsMemNodeHead 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 是内存池起始地址,UINT32 allocSize 是从新申请的内存的大小,struct OsMemNodeHead 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 是内存池起始地址,UINT32 allocSize 是从新申请的内存的大小,struct OsMemNodeHead node 是以后须要从新分配内存的内存节点,UINT32 nodeSize 是以后节点的大小,struct OsMemNodeHead *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 初始化动态内存池

咱们剖析下初始化动态内存池函数 UINT32 LOS_MemInit(VOID pool, UINT32 size)的代码。咱们先看看函数参数,VOID pool 是动态内存池的起始地址,UINT32 size 是初始化的动态内存池的总大小,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()。⑴处设置动态内存池信息结构体 struct OsMemPoolHead *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 为内存池起始地址,UINT32 size 为须要申请的内存大小,UINT32 boundary 内存对齐数值。当申请内存后失去的内存地址 VOID ptr,对齐后的内存地址为 VOID *alignedPtr,二者的偏移值应用 UINT32 gapSize 保留。因为曾经按 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 开释动态内存

对申请的内存块应用结束,咱们能够应用函数 UINT32 LOS_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 为之前申请的内存地址,UINT32 size 为从新申请的内存大小。返回值为新内存块地址,或者返回 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;
}

小结

本文率领大家一起分析了鸿蒙轻内核的动静内存模块的源代码,蕴含动态内存的构造体、动态内存池初始化、动态内存申请、开释等。感激浏览,如有任何问题、倡议,都能够留言给咱们:https://gitee.com/openharmony…。为了更容易找到鸿蒙轻内核代码仓,倡议拜访 https://gitee.com/openharmony…,关注 Watch、点赞 Star、并 Fork 到本人账户下,谢谢。

点击关注,第一工夫理解华为云陈腐技术~

正文完
 0