内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。
在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和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
内核为例,均可以在开源站点 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
,还会维护内存池的水线数值。
objectivec
代码解读
复制代码
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
后文会介绍。
ini
代码解读
复制代码
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[]
。⑶处的成员变量是个指针组合体,内存池中的每个内存节点头维护指针执行上一个内存节点。⑷处维护内存节点的大小和标记信息。
objectivec
代码解读
复制代码
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
作为唯一的成员。
arduino
代码解读
复制代码
struct OsMemUsedNodeHead { struct OsMemNodeHead header; };
我们再看下空闲内存节点结构体OsMemFreeNodeHead
,除了动态内存节点头结构体OsMemNodeHead
成员,还包含2个指针分别指向上一个和下一个空闲内存节点。
arduino
代码解读
复制代码
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个二级小区间。
objectivec
代码解读
复制代码
/* 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_