LiteOS-M动态内存管理
LiteOS-M的动态内存管理将大块的预分配内存,通过最佳适应算法,动态分配给申请者。动态内存分配算法都无法避免内存碎片化,但通过最佳适应算法能够最大限度的减少内存碎片化。
代码实现文件:
kernel/liteos_m/kernel/mm/los_memory.c
kernel/liteos_m/kernel/include/los_memory.h
内存池初始化流程
LiteOS-M的内存池的初始化通过调用函数OsMemPoolInit()来完成。
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); //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; //内存块size为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;
}
- 首先将内存池的顶部作为内存池的管理信息,设置内存池的起始地址、大小和缺省属性;
- 然后将剩余的内存作为一个空闲内存块,并加入空闲内存列表中;
- 最后在内存池底部设置结束内存块(内存块size等于0、且标记为已使用);
内存管理信息
一个内存池是通过内存池管理信息和内存块信息来共同管理的。内存池管理信息在内存池的头部,内存块信息则在每个空闲或已分配内存的头部。用户申请内存,实际分配的内存空间还包含了内存块信息,只是这部分对用户不可见。
所有内存块信息通过prev指针组成单向循环链表,且相邻内存块的地址是有序的。通过prev指针能够找到前一个内存块的地址;由于没有使用next指针,但相邻内存块是地址是有序的,因此通过当前内存块的起始地址+内存块的大小,也可以找到下一个相邻的内存块。
内存池的管理信息和内存块管理信息分布:
内存池管理信息结构和解析
struct OsMemPoolInfo {
VOID *pool;
UINT32 totalSize;
UINT32 attr;
#if (LOSCFG_MEM_WATERLINE == 1)
UINT32 waterLine; /* Maximum usage size in a memory pool */
UINT32 curUsedSize; /* Current usage size in a memory pool */
#endif
};
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
};
- OsMemPoolInfo:内存池信息
- pool:内存池地址
- totalSize:内存池大小
- attr:内存管理扩展属性
- waterLine:记录内存消耗水位
- curUsedSize:记录当前消耗的内存
- freeListBitmap:空闲内存块位图,标记所对应的空闲内存块是否存在
freeListBitmap数组成员是32位整数,数组成员数量=空闲块数组数量/32+1(223/32+1=7),数组成员的每个bit对应一个OsMemFreeNodeHead数组成员,以此来标识空闲内存块的状态。 - OsMemFreeNodeHead:空闲内存块的数组
OsMemFreeNodeHead用于管理空闲内存,每个数组成员对应一个内存分组。数组成员共有31+24*8=223个,其中下标为0-30的31个数组成员管理128字节以内的小空闲内存块,每4字节为一个内存分组;另外下标为31-222的192个数组成员管理超过或等于128字节的空闲内存块,划分方法是通过2的n次幂(n=7~31)确定空闲内存块的一级内存分组边界,每个一级内存分组又分成8等份形成大空闲内存块使用的二级内存分组(如下图)。同一分组的空闲内存块通过prev和next指针组成双向链表。
OsMemFreeNodeHead数组成员:- header:内存块信息
- prev:前一个空闲内存块的指针
- next:下一个空闲内存块的指针
- nextPool:当支持多内存池配置时,指向下一个内存池
内存块信息结构和解析
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
};
- magic:用于内存完整性检查的魔数
- linkReg:用于内存泄漏检测的队列信息
- prev:指向前一个内存块指针
- next:指向下一个内存块指针
- taskID:分配内存的进程ID,如果开启了内存完整性检测功能
- sizeAndFlag:0-21位是内存块大小;22-31位是内存标识,如已分配标记、对齐标记、内存泄露标记等
动态内存管理原理
LiteOS-M动态内存管理系统通过分组来管理内存的,如上节所述,会将所有内存分成223个分组,每个分组负责管理特定大小的空闲内存。我们知道,在内存池初始化时会将所有剩余内存转换成一个很大的空闲内存块,而这个空闲内存块就会像一个大蛋糕一样,一点点被分割出去。申请时切割后的空闲内存块会按大小重新分组,释放时也会将回收内存块按大小进行分组。
空闲内存块分组的管理是通过内存池管理信息的2个成员freeListBitmap和OsMemFreeNodeHead来完成。它们的映射关系如下:
freeListBitmap的每个bit都表示某个分组是否存在空闲内存块,以此来加快内存申请的速度。OsMemFreeNodeHead则是每个空闲内存分组的头节点,同一分组的所有空闲内存块信息都和头节点组成双向链表。如下图,内存分组4的管理内存大小是20—23字节的所有空闲内存块,而内存分组0管理内存大小是4—7字节的所有空闲内存块。
内存申请
内存在申请时,如果开启了内存完整性检测功能,会首先检测所有的内存块,确定没有异常才会继续申请内存。然后通过最佳适应算法查找空闲内存块,如果找到的空闲内存块过大,还需要将内存块分割成2部分,一个和申请的内存一样大小,另外一个重新回到空闲内存分组列表中。最后申请到的空闲内存块需要做好已使用的标记,并返回申请的内存地址。
分配内存的算法
LiteOS-M采用的是最佳适应法,具体的方法如下:
- 首先依据申请内存的大小计算出所属空闲内存组;
- 然后在更大一级分组(内存大小肯定要比申请的大)查找是否存在空闲内存块,如果有则在该分组找最小的分配出去;
- 如果第二步没有找到,则从小到大开始检索后面所有的内存分组,直到找到空闲内存块;
- 在以上方法都没有找到的情况下,就会在所属内存分组中查找是否存在合适的空闲内存块。(为何不首先在所属内存分组内查找呢?)
内存释放
内存释放时首先需要检查内存的有效性,然后清除该内存块的使用标记,之后检查相邻的内存块是否空闲,如果是则合并成一个大块。最后将该内存块加入空闲内存块列表中。
内存使用接口
接口 | 描述 |
---|---|
LOS_MemInit | 初始化内存池 |
LOS_MemAlloc | 分配内存 |
LOS_MemFree | 释放内存 |