鸿蒙轻内核M核源码分析系列七 动态内存Dynamic Memory
内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。
在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。
鸿蒙轻内核的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。
-
动态内存:在动态内存池中分配用户指定大小的内存块。
- 优点:按需分配。
- 缺点:内存池中可能出现碎片。
-
静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。
- 优点:分配和释放效率高,静态内存池中无碎片。
- 缺点:只能申请到初始化预设大小的内存块,不能按需申请。
上一系列分析了静态内存,我们开始分析动态内存。动态内存管理主要用于用户需要使用大小不等的内存块的场景。当用户需要使用内存时,可以通过操作系统的动态内存申请函数索取指定大小的内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。
OpenHarmony LiteOS-M动态内存在TLSF算法的基础上,对区间的划分进行了优化,获得更优的性能,降低了碎片率。动态内存核心算法框图如下:

根据空闲内存块的大小,使用多个空闲链表来管理。根据内存空闲块大小分为两个部分:[4, 127]和[2^7, 2^31],如上图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/kernel_liteos_m 获取。接下来,我们看下动态内存的结构体,动态内存初始化,动态内存常用操作的源代码。
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];#endifunion {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;#elseUINT32 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算法相关的宏定义和内联函数,这些宏非常重要,在分析源代码前需要熟悉下这些宏的定义。可以结合上文的动态内存核心算法框图进行学习。⑴处的宏对处于[2n,2(n+1)],其中(n=7,8,…30)区间的大内存块进行2^3=8等分。⑵处的宏,定义处于[4,127]区间的小内存块划分为31个,即4,8,12,…,124。⑶处定义小内存的上界值,考虑内存对齐和粒度,最大值只能取到124。
⑷处的宏表示处于[2n,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个级别,对处于[2n,2(n+1)],其中(n=7,8,…30)区间的内存,有24个级别。⒂处根据内存大小,内存区间一级编号获取获取二级小区间的编号,对处于[2n,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_TRACELOS_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);
#endifOsHookCall(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_ENABLEendNode->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;
#endifreturn 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_TRACEUINT64 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_TRACEUINT64 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);
#endifOsHookCall(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_ENABLEif (pool->info.attr & OS_MEM_POOL_EXPAND_ENABLE) {INT32 ret = OsMemPoolExpand(pool, allocSize, intSave);if (ret == 0) {goto retry;}}
#endifPRINT_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_TRACEUINT64 start = HalClockGetCycles();
#endifUINT32 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_TRACEUINT64 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);
#endifOsHookCall(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_TRACEUINT64 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_TRACEUINT64 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);
#endifOsHookCall(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_TRACEUINT64 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_TRACEUINT64 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);
#endifreturn 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;
}
小结
本文带领大家一起剖析了鸿蒙轻内核的静态内存模块的源代码,包含动态内存的结构体、动态内存池初始化、动态内存申请、释放等。
如果大家想更加深入的学习 OpenHarmony 开发的内容,不妨可以参考以下相关学习文档进行学习,助你快速提升自己:
OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ……

系统架构分析:https://qr18.cn/CgxrRy
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

相关文章:
鸿蒙轻内核M核源码分析系列七 动态内存Dynamic Memory
内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。 在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优&#x…...
从头搭hadoop集群--分布式hadoop集群搭建
模板虚拟机安装配置见博文:https://blog.csdn.net/weixin_66158110/article/details/139236148 配置文件信息如下:https://pan.baidu.com/s/1074eD5aNVugEPcjwVvi9jA?pwdl1xq(提取码:l1xq) hadoop版本:h…...
odoo10 权限控制用户只允许看到自己的字段
假设一个小区管理员用户,只想看到自己小区的信息。 首先添加一个用户信息选项卡界面,如下图的 用户 > 隶属信息: 我们在自己创建的user模块中,views文件夹下添加base_user.xml <?xml version"1.0" encoding&q…...
图解Mysql索引原理
概述 是什么 索引像是一本书的目录列表,能根据目录快速的找到具体的书本内容,也就是加快了数据库的查询速度索引本质是一个数据结构索引是在存储引擎层,而不是服务器层实现的,所以,并没有统一的索引标准,…...
Arduino网页服务器:如何将Arduino开发板用作Web服务器
大家好,我是咕噜铁蛋!今天,我将和大家分享一个有趣且实用的项目——如何使用Arduino开发板搭建一个简易的网页服务器。通过这个项目,你可以将Arduino连接到互联网,并通过网页控制或查询Arduino的状态。 一、项目背景与…...
大模型日报2024-06-05
大模型日报 2024-06-05 大模型资讯 AI气象预测取得重大进展:单台桌面电脑即可运行全球天气模型 摘要: 一项新的人工智能天气预测模型已经取得重大进展,该模型能够在一台普通的桌面电脑上运行,预测全球天气。这意味着即使没有复杂的物理计算&a…...
LLM 大模型学习必知必会系列(二):提示词工程-Prompt Engineering 以及实战闯关
角色扮演:在系统指令中告诉千问你需要它扮演的角色,即可沉浸式和该角色对话交流语言风格:简单调整 LLM 的语言风格任务设定:比如旅行规划,小红书文案助手这样的专项任务处理System message 也可以被用于规定 LLM 的答复…...
Spring系统学习 - Spring入门
什么是Spring? Spring翻译过来就是春天的意思,字面意思,冠以Spring的意思就是想表示使用这个框架,代表程序员的春天来了,实际上就是让开发更加简单方便,实际上Spring确实做到了。 官网地址:ht…...
Priority_queue
一、priority_queue的介绍和使用 1.1 priority_queue的介绍 1.优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。 2.优先队列类似于堆, 在堆中可以随时插入元素, 并且只能检索最大堆…...
SpringMVC:获取请求数据
1. 通过RequestParma注解接收 /**** value和name都可以使用,互为别名* 如果此处设置了需要什么参数而前端请求时没有提供则会报400(请求参数不一致错误)* required参数用于设置该参数是否为必须传递参数,默认为true必须传递* defa…...
深度学习 --- stanford cs231 编程作业(assignment1,Q2: SVM分类器)
stanford cs231 编程作业之SVM分类器 写在最前面: 深度学习,或者是广义上的任何学习,都是“行千里路”胜过“读万卷书”的学识。这两天光是学了斯坦福cs231n的一些基础理论,越往后学越觉得没什么。但听的云里雾里的地方也越来越多…...
【scikit-learn010】sklearn算法模型清单实战及经验总结(已更新)
1.一直以来想写下基于scikit-learn训练AI算法的系列文章,作为较火的机器学习框架,也是日常项目开发中常用的一款工具,最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下scikit-learn框架模型算法包相关技术点及经验。 3.欢迎批评指正,欢迎互三,跪谢一键…...
Rethinking overlooked aspects in vision-language models
探讨多模态视觉语言模型的一些有趣结论欢迎关注 CVHub!https://mp.weixin.qq.com/s/zouNu-g-33_7JoX3Uscxtw1.Introduction 多模态模型架构上的变化不大,数据的差距比较大,输入分辨率和输入llm的视觉token大小是比较关键的,适配器,VIT和语言模型则不是那么关键。InternVL-…...
【漯河市人才交流中心_登录安全分析报告-Ajax泄漏滑动距离导致安全隐患】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
C语言—字符函数和字符串函数
1.字符分类函数 C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。 这些函数的使用都需要包含一个头文件 ctype.h。 例:将一句话中的小写字母改成大写字母。 2.字符转换函数 头文件:ctype.h C语言提供了2…...
爬山算法的详细介绍
爬山算法(Hill Climbing Algorithm)是一种基于启发式的局部搜索算法,常用于解决优化问题。它的核心思想是从当前解的邻域中选择能够使目标函数值最大(或最小)的下一个解作为当前解,直到找到一个满足问题要求…...
硕士课程 可穿戴设备之作业一
作业一 第一个代码使用的方法是出自于[1]。 框架结构 如下图,不过根据对代码的解读,发现作者在代码中省去了对SSR部件的实现,下文再说。 Troika框架由三个关键部件组成:信号分解,SSR和光谱峰值跟踪。(粗…...
测试记录3:WLS2运行Linux界面
1.WLS1转到WLS2 (1)根据自己的平台,下载WLS2安装包 x64: https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi arm64: https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_arm64.msi (2&…...
好用软件推荐
软件功能相关介绍地址FastStone截图(长截图、定时截图等)CSDNhttps://www.faststone.org/FSCaptureDownload.htmQuicker快捷访问https://getquicker.net/https://getquicker.net/...
王学岗鸿蒙开发(北向)——————(二)TS基本语法详解
1,Ts(TypeScript)语法相当于JAVAScript类型,鸿蒙arkTs是基于TS语言的,当然artTs也融合了其它的语言。 2,本篇文章是基于n9版本。注意,有些语法是已经不能用的。 3, 4,变量:用来存储数据,数字字母组成,数字不…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...
什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
前端高频面试题2:浏览器/计算机网络
本专栏相关链接 前端高频面试题1:HTML/CSS 前端高频面试题2:浏览器/计算机网络 前端高频面试题3:JavaScript 1.什么是强缓存、协商缓存? 强缓存: 当浏览器请求资源时,首先检查本地缓存是否命中。如果命…...
