系统运行,少不了做些内存操作,MemAlloc / MemFree,就要实行内存管理。
以LiteOS为基础,所以设计上需要看LiteOS的相关文档 https://support.huaweicloud.com/LiteOS/index.html
在HI3559V200 SDK开发包中也有liteos内核开发文档,其中也描述了内存管理算法。
网页资料看https://support.huaweicloud.com/kernelmanual-LiteOS/zh-cn_topic_0145350130.html
基本概念
内存管理模块管理系统的内存资源,它是操作系统的核心模块之一。主要包括内存的初始化、分配以及释放。
在系统运行过程中,内存管理模块通过对内存的申请/释放操作,来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。
Huawei LiteOS的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。
动态内存:在动态内存池中分配用户指定大小的内存块。
优点:按需分配。
缺点:内存池中可能出现碎片。
静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。
优点:分配和释放效率高,静态内存池中无碎片。
缺点:只能申请到初始化预设大小的内存块,不能按需申请。
动态内存运作机制
动态内存管理,即在内存资源充足的情况下,从系统配置的一块比较大的连续内存(内存池),根据用户需求,分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。
与静态内存相比,动态内存管理的好处是按需分配,缺点是内存池中容易出现碎片。
LiteOS动态内存支持DLINK和BEST LITTLE两种标准算法。
1.DLINK
DLINK动态内存管理结构如图1所示:
图1
第一部分:堆内存(也称内存池)的起始地址及堆区域总大小。
第二部分:本身是一个数组,每个元素是一个双向链表,所有free节点的控制头都会被分类挂在这个数组的双向链表中。
假设内存允许的最小节点为2min字节,则数组的第一个双向链表存储的是所有size为2min<size< 2min+1的free节点,第二个双向链表存储的是所有size为2min+1<size< 2min+2的free节点,依次类推第n个双向链表存储的是所有size为2min+n-1<size< 2min+n的free节点。每次申请内存的时候,会从这个数组检索最合适大小的free节点,进行分配内存。每次释放内存时,会将该片内存作为free节点存储至这个数组,以便下次再利用。
第三部分:占用内存池极大部分的空间,是用于存放各节点的实际区域。以下是LOS_MEM_DYN_NODE节点结构体申明以及简单介绍:
typedef struct tagLOS_MEM_DYN_NODE
{
LOS_DL_LIST stFreeNodeInfo;
struct tagLOS_MEM_DYN_NODE *pstPreNode;
UINT32 uwSizeAndFlag;
}LOS_MEM_DYN_NODE;
图2
2.BEST LITTLE
LiteOS的动态内存分配支持最佳适配算法,即BEST LITTLE,每次分配时选择内存池中最小最适合的内存块进行分配。LiteOS动态内存管理在最佳适配算法的基础上加入了SLAB机制,用于分配固定大小的内存块,进而减小产生内存碎片的可能性。
LiteOS内存管理中的SLAB机制支持可配置的SLAB CLASS数目及每个CLASS的最大空间,现以SLAB CLASS数目为4,每个CLASS的最大空间为512字节为例说明SLAB机制。在内存池中共有4个SLAB CLASS,每个SLAB CLASS的总共可分配大小为512字节,第一个SLAB CLASS被分为32个16字节的SLAB块,第二个SLAB CLASS被分为16个32字节的SLAB块,第三个SLAB CLASS被分为8个64字节的SLAB块,第四个SLAB CLASS被分为4个128字节的SLAB块。这4个SLAB CLASS是从内存池中按照最佳适配算法分配出来的。
初始化内存管理时,首先初始化内存池,然后在初始化后的内存池中按照最佳适配算法申请4个SLAB CLASS,再逐个按照SLAB内存管理机制初始化4个SLAB CLASS。
每次申请内存时,先在满足申请大小的最佳SLAB CLASS中申请,(比如用户申请20字节内存,就在SLAB块大小为32字节的SLAB CLASS中申请),如果申请成功,就将SLAB内存块整块返回给用户,释放时整块回收。如果满足条件的SLAB CLASS中已无可以分配的内存块,则继续向内存池按照最佳适配算法申请。需要注意的是,如果当前的SLAB CLASS中无可用SLAB块了,则直接向内存池申请,而不会继续向有着更大SLAB块空间的SLAB CLASS申请。
释放内存时,先检查释放的内存块是否属于SLAB CLASS,如果是SLAB CLASS的内存块,则还回对应的SLAB CLASS中,否则还回内存池中。
静态内存运作机制
静态内存实质上是一块静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更。
静态内存池由一个控制块和若干相同大小的内存块构成。控制块位于内存池头部,用于内存块管理。内存块的申请和释放以块大小为粒度。
图3 静态内存示意图
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
对应目录为 kernel\liteos_a\kernel\base\mem,有bestfit , bestfit_little , common , membox
以学习了解为目的,所以需要快速实现,自己动手简化DLINK算法,
/*
LHn include nodeLH with size 2^(2*n) ~ 2^(2*n+1)
[0] 1 [10] [1K, 2K) [20] [1M, 2M)
[1] [2, 4) [11] [2K, 4K) [21] [2M, 4M)
[2] [4, 8) [12] [4K, 8K) [22] [4M, 8M)
[3] [8, 16) [13] [8K, 16K) [23] [8M, 16M)
[4] [16, 32) [14] [16K, 32K) [24] [16M, 32M)
[5] [32, 64) [15] [32K, 64K) [25] [32M, 64M)
[6] [64, 128) [16] [64K, 128K) [26] [64M, 128M)
[7] [128, 256) [17] [128K, 256K) [27] [128M, 256M)
[8] [256, 512) [18] [256K, 512K) [28] [256M, 512M)
[9] [512, 1K) [19] [512K, 1M) [29] [512M, 1G)
-----------------------------------------------------
LinkNode + MemNode(head + userspace)
*/
去掉DLINK的第一部分,
DLINK的第二部分作为这里的第一部分:本身是一个数组,每个元素是一个双向链表,所有free节点的控制头都会被分类挂在这个数组的双向链表中。
LinkNode[0]下面挂的是大小为1字节的内存节点,LinkNode[1]下挂的是大小为2字节或3字节的内存节点。
(这两个是否有必要存在,这里不考虑)
每次申请内存的时候,会从这个数组检索大于需分配大小的最小范围的free节点进行分配内存。
每次释放内存时,首先检查能否合入左右free的节点中。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------