摘要:本文为大家剖析 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 算法的源代码、开发文档,示例程序代码如下:
包括动态内存的 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 到自己账户下,谢谢。
点击关注,第一时间了解华为云新鲜技术~
评论