参考:
- 【野火】物联网操作系统 LiteOS 开发实战指南
- Huawei LiteOS | 中文网
8. 内存管理
8.1 基本概念
8.1.1 概念
- LiteOS操作系统将
内核
与内存管理
分开实现,操作系统内核仅规定了必要的内存管理函数原型,而不关心这些内存管理函数是如何实现的,所以在 LiteOS 中提供了多种内存分配算法(分配策略),但是上层接口(API)却是统一的 - LiteOS内存管理模块管理用于系统的内存资源,包括:
- 初始化
- 分配
- 释放
- 不采用C标准库中的内存管理函数
malloc()
和free()
原因:- 小型嵌入式设备的RAM的不足,导致这些函数有些情况无法使用
- 他们的实现可能非常大,占据了相当大的一块代码空间
- 不安全,执行时间不确定
- 容易产生碎片,这两个函数会使得连接器配置变得非常复杂
8.1.2 特点
- 内存管理模块通过对内存的释放、申请操作,来管理用户和OS对内存的使用,使内存的利用率和使用率达到最优,同时最大限度地解决系统的内存碎片问题
- 内存管理分为:
静态内存管理
和动态内存管理
- 静态内存管理:在静态内存池中分配用户初始化时预设(固定)大小的内存块
- 优点:分配和释放效率高,静态内存池中无碎片
- 缺点:智能申请到初始化预设的内存块,不能按需申请
- 动态内存:在动态内存池中分配用户指定大小的内存块
- 优点:按需分配
- 内存池可能存在碎片
- 静态内存管理:在静态内存池中分配用户初始化时预设(固定)大小的内存块
- LiteOS给提供了多种内存动态匹配算法默认使用
BestFit
(最佳适应算法),其他算法还包括DLINK
算法BestFit
算法:每次为作业分配内存时,总是把能满足要求,又是最小的空闲分区分配给作业,避免“大材小用”
8.2 应用场景
- 内存管理的主要工作:动态划分并管理用户分配好的内存空间,主要是在用户需要使用大小不等的内存块的使用场景
- 当用户需要分配内存时,可以通过操作系统的内存申请函数索取指定大小,一旦使用完毕,通过动态内存的释放函数归还占用内存,使之可以重复使用
8.3 内存运作机制
8.3.1 动态内存运作机制(BestFit算法)
动态内存管理,即在内存资源充足的情况下,从系统配置一块比较大的连续内存(内存池,其大小为OS_SYS_MEM_SIZE
),根据用户的需求,分配任意大小的内存块,当用户不需要该内存块时,又可以释放回系统供下一次使用。
LiteOS 动态内存管理在最佳适配算法的基础上加入了 SLAB 机制,用于分配固定大小的内存块,进而减小产生内存碎片的可能性。
- 初始化内存,调用
LOS_MemInit
- 首先初始化一个内存池
- 在初始化后的内存池中生成一个内存信息管理节点(
LOS_HEAP_MANAGER
),如上图第一部分 - 申请 n 个
SLAB CLASS
,再逐个按照 SLAB 内存管理机制初始化 n 个SLAB CLASS
,每个SLAB CLASS
都有一个LOS_HEAP_NODE
进行管理
- 申请内存,调用
LOS_MemAlloc
- 每次申请内存时,先在满足申请大小的最佳
SLAB CLASS
中申请 - 申请成功,将SLAB内存块整块返回给用户,释放时整块回收
- 如果满足条件的
SLAB CLASS
中已无可以分配(最佳的,而不是更大的)的内存块,则继续向内存池按照最佳适配算法申请
- 每次申请内存时,先在满足申请大小的最佳
- 释放内存,调用
LOS_MemFree
- 释放内存时,先检查释放的内存块是否属于
SLAB CLASS
,如果是SLAB CLASS
的内存块,则归还对应的SLAB CLASS
,否则归还内存池中
- 释放内存时,先检查释放的内存块是否属于
8.3.2 静态内存运作机制
-
静态内存实质上是一块静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更
-
静态内存池由一个控制块和若干个相同大小的内存块构成。控制块位于内存池头部,用于内存块管理。
-
内存块的申请和释放以块大小为粒度
8.4 开发说明
8.4.1 静态内存使用
注意:使用时需要包含“
los_membox.h
”
-
使用场景:当用户需要使用固定长度的内存时,可以使用静态内存分配的方式获取内存
-
功能函数
Huawei LiteOS的静态内存管理主要为用户提供以下功能。
功能分类 接口名 描述 初始化静态内存 LOS_MemboxInit 初始化一个静态内存池,设定其起始地址、总大小及每个块大小 清除静态内存内容 LOS_MemboxClr 清零静态内存块 申请一块静态内存 LOS_MemboxAlloc 申请一块静态内存块 释放内存 LOS_MemboxFree 释放一个静态内存块 分析静态内存池状态 LOS_MemboxStatisticsGet 获取静态内存池的统计信息 -
开发流程:
- 开发一片内存区域作为静态内存池
- 调用
LOS_MemboxInit()
接口,进行静态内存使用前的初始化,将入参指定的内存区域分割为N块(N值取决于静态内存总大小和块大小),将所有内存块挂到空闲链表,在内存起始处防止控制头 - 调用
LOS_MemboxAlloc()
接口,系统将会从空闲链表中获取第一个空闲块,并返回该块的用户空间地址 - 调用
LOS_MemboxFree()
接口,将该块内存加入空闲块链表 - 调用
LOS_MemboxClr()
接口,系统内部清零静态内存块,将入参地址对应的内存块清零
-
静态内存控制块
typedef struct { UINT32 uwBlkSize; /* 内存块的大小 */ UINT32 uwBlkNum; /* 内存块总数 */ UINT32 uwBlkCnt; /* 已经分配使用的块数 */ LOS_MEMBOX_NODE stFreeList; /* 内存控制块的空闲链表,指针指向下一个可用的内存块 */ } LOS_MEMBOX_INFO;
-
静态内存初始化函数
LOS_MemboxInit()
UINT32 LOS_MemboxInit(VOID *pBoxMem, /* 内存池地址,需要用户自定义 */ UINT32 uwBoxSize, /* 内存池大小 */ UINT32 uwBlkSize); /* 内存块大小 */
初始化后内存示意图:
-
静态内存申请函数
LOS_MemboxAlloc()
VOID *LOS_MemboxAlloc(VOID *pBoxMem) /* 指向内存池的指针 */
申请内存示意图如下:
-
静态内存释放函数
LOS_ MemboxFree()
UINT32 LOS_MemboxFree(VOID *pBoxMem, /* 内存池地址 */ VOID *pBox) /* 需要释放的内存块 */
内存释放示意图:
-
静态内存内容清除函数
LOS_MemboxClr()
VOID LOS_MemboxClr(VOID *pBoxMem, /* 内存池地址 */ VOID *pBox) /* 需要释放的内存块 */
-
注意事项
- 静态内存池区域,可以通过定义全局数组或调用动态内存分配接口方式获取
- 如果使用动态内存分配方式,在不需要静态内存池时,注意释放该段内存,避免内存泄露
8.4.2 动态内存使用
**注意:**使用时需要包含“
los_memory.h
”
-
应用场景
- 主要是在用户需要使用大小不等的内存块的场景中使用
- 当用户需要分配内存时,可以通过操作系统的动态内存申请函数索取指定大小内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用
-
功能函数
Huawei LiteOS 系统中的动态内存管理模块为用户提供下面几种功能,具体的API详见接口手册。
功能分类 接口名 描述 内存初始化 LOS_MemInit
初始化一块指定的动态内存池,大小为size。 申请动态内存 LOS_MemAlloc
从指定动态内存池中申请size长度的内存。 释放动态内存 LOS_MemFree
释放已申请的内存。 重新申请内存 LOS_MemRealloc
按size大小重新分配内存块,并保留原内存块内容。 内存对齐分配 LOS_MemAllocAlign
从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存。 分析内存池状态 LOS_MemStatisticsGet
获取指定内存池的统计信息 查看内存池中最大可用空闲块 LOS_MemGetMaxFreeBlkSize
获取指定内存池的最大可用空闲块 -
开发流程
- 配置:
OS_SYS_MEM_ADDR
:系统动态内存池起始地址,需要用户指定OS_SYS_MEM_SIZE
:系统动态内存池大小,以 byte 为单位,需要用户正确计算LOSCFG_MEMORY_BESTFIT
:置为 YES,选中内存管理算法中的 BESTFIT 算法LOSCFG_KERNEL_MEM_SLAB
:置为 YES,打开内存管理中的SLAB机制SLAB_MEM_COUNT
:该配置位于内核中,一般不需要改动,表示 SLAB CLASS 的数量,目前内核初始化为 4SLAB_MEM_ALLOCATOR_SIZE
:该配置位于内核中,一般不需要改动,表示每个 SLAB CLASS 的最大可分配的块的总空间SLAB_BASIC_NEED_SIZE
:该配置位于内核中,一般不需要改动,表示初始化 SLAB 机制时需要的最小的堆空间。如果改动了SLAB_MEM_COUNT
和SLAB_MEM_ALLOCATOR_SIZE
的配置,就需要同步改动这个配置
- 初始化:
- 调用
LOS_MemInit
函数初始化用户指定的动态内存池,若用户使能了 SLAB 机制并且内存池中的可分配内存大于 SLAB 需要的最小内存,则会进一步初始化 SLAB CLASS
- 调用
- 申请任意大小的动态内存:
- 调用
LOS_MemAlloc
函数从指定的内存池中申请指定大小的内存块,申请时内存管理先向 SLAB CLASS 申请,申请失败后继续向堆内存空间申请,最后将申请结果返回给用户。在向堆内存空间申请时,会存在内存块的切分
- 调用
- 释放动态内存:
- 调用
LOS_MemFree
函数向指定的动态内存池释放指定的内存块,释放时会先判断该内存块是否属于 SLAB CLASS,若属于,则将该内存块还回 SLAB CLASS。否则,向堆内存空间释放内存块。在向堆内存空间释放时,会存在内存块的合并
- 调用
- 配置:
-
动态内存初始化函数
LOS_MemInit()
UINT32 LOS_MemInit(VOID *pPool, /* 内存开始地址 */ UINT32 uwSize) /* 内存块大小 */
-
动态内存申请函数
LOS_MemAlloc()
VOID *LOS_MemAlloc (VOID *pPool, /* 内存池地址 */ UINT32 uwSize) /* 分配的内存大小 */
申请内存的时候,系统是从内存信息管理节点的 pstTail 指向的内存块开始遍历整个内存块链表查找合适的内存块,如果某个空闲内存块的大小能容得下用户要申请的内存,则将这块内存取出用户需要内存空间大小的部分返回给用户,剩下的内存块组成一个新的空闲块,插入到空闲内存块链表中
内存分配完成示意图:
-
动态内存释放函数
LOS_MemFree()
UINT32 LOS_MemFree (VOID *pPool, /* 内存池地址 */ VOID *pMem) /* 需要释放的内存地址 */
- 注意事项
- 由于系统中动态内存管理需要消耗管理控制块结构的内存,故实际用户可使用空间总量小于在配置文件
los_config.h
中配置项OS_SYS_MEM_SIZE
的大小 - 系统中地址对齐申请内存分配
LOS_MemAllocAlign
可能会消耗部分对齐导致的空间,故存在一些内存碎片,当系统释放该对齐时,同时回收由于对齐导致的内存碎片 - 系统中重新分配内存
LOS_MemRealloc
函数如果分配成功,系统会自己判断是否需要释放原来申请的空间,返回重新分配的空间。用户不需要手动释放原来的空间 - 系统中多次调用
LOS_MemFree
时,第一次会返回成功,但对同一块内存进行多次重复释放会导致非法指针操作,导致结果不可预知
- 由于系统中动态内存管理需要消耗管理控制块结构的内存,故实际用户可使用空间总量小于在配置文件