4.2 内存

概述

基本概念

内存管理模块管理系统的内存资源,它是操作系统的核心模块之一。主要包括内存的初始化、分配以及释放。

在系统运行过程中,内存管理模块通过对内存的申请/释放操作,来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。

Huawei LiteOS的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。

动态内存: 在动态内存池中分配用户指定大小的内存块。

  • 优点:按需分配。
  • 缺点:内存池中可能出现碎片。

静态内存: 在静态内存池中分配用户初始化时预设(固定)大小的内存块。

  • 优点:分配和释放效率高,静态内存池中无碎片。
  • 缺点:只能申请到初始化预设大小的内存块,不能按需申请。

动态内存运作机制

动态内存管理,即在内存资源充足的情况下,从系统配置的一块比较大的连续内存(内存池),根据用户需求,分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。

与静态内存相比,动态内存管理的好处是按需分配,缺点是内存池中容易出现碎片。

LiteOS动态内存支持DLINK和BEST LITTLE两种标准算法。

1.DLINK

DLINK动态内存管理结构如图1所示:

图 1 DLINK动态内存管理结构图
4.2 内存 - 图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节点结构体申明以及简单介绍:

  1. typedef struct tagLOS_MEM_DYN_NODE
  2. {
  3. LOS_DL_LIST stFreeNodeInfo;
  4. struct tagLOS_MEM_DYN_NODE *pstPreNode;
  5. UINT32 uwSizeAndFlag;
  6. }LOS_MEM_DYN_NODE;

图 2 LOS_MEM_DYN_NODE节点结构图 4.2 内存 - 图2

2.BEST LITTLE

LiteOS的动态内存分配支持最佳适配算法,即BEST LITTLE,每次分配时选择堆内存(内存池中)最小最适合的内存块进行分配。

图 3 BEST LITTLE动态内存管理结构图 4.2 内存 - 图3

静态内存运作机制

静态内存实质上是一块静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更。

静态内存池由一个控制块和若干相同大小的内存块构成。控制块位于内存池头部,用于内存块管理。内存块的申请和释放以块大小为粒度。

图 4 静态内存示意图
4.2 内存 - 图4

动态内存

开发指导

使用场景

内存管理的主要工作是动态的划分并管理用户分配好的内存区间。

动态内存管理主要是在用户需要使用大小不等的内存块的场景中使用。

当用户需要分配内存时,可以通过操作系统的动态内存申请函数申请指定大小内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。

功能

Huawei LiteOS系统中的动态内存管理模块为用户提供下面几种功能,具体的API详见接口手册。

功能分类

接口名

描述

内存初始化

LOS_MemInit

初始化一块指定的动态内存池,大小为size。

删除内存池

LOS_MemDeInit

删除指定内存池,仅打开LOSCFG_MEM_MUL_POOL时有效。

申请动态内存

LOS_MemAlloc

从指定动态内存池中申请size长度的内存。

释放动态内存

LOS_MemFree

释放已申请的内存。

重新申请内存

LOS_MemRealloc

按size大小重新分配内存块,并保留原内存块内容。

内存对齐分配

LOS_MemAllocAlign

从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存。

获取内存大小

LOS_MemPoolSizeGet

获取指定的动态内存池总大小。

获取内存大小

LOS_MemTotalUsedGet

获取指定动态内存池的总使用量大小。

获取内存块数量

LOS_MemFreeBlksGet

获取指定内存池的空闲内存块数量。

获取内存块数量

LOS_MemUsedBlksGet

获取指定内存池的已使用的内存块数量。

获取分配指定内存区域的任务ID

LOS_MemTaskIdGet

获取分配了指定内存区域的任务ID。

获取内存池中最后一个使用内存块的结束地址

LOS_MemLastUsedGet

获取内存池最后一个使用内存块的结束地址。

获取内存结构信息

LOS_MemInfoGet

获取指定内存池的内存结构信息。

对指定内存池做完整性检查

LOS_MemIntegrityCheck

对指定内存池做完整性检查。

获取内存块大小

LOS_MemNodeSizeCheck

获取内存块的总大小和可操作大小。

设定内存检查级别

LOS_MemCheckLevelSet

设定内存检查级别。

获取内存检查级别

LOS_MemCheckLevelGet

获取内存检查级别。

显示系统内存池

LOS_MemPoolList

打印显示系统中已初始化的所有内存池,仅打开LOSCFG_MEM_MUL_POOL时有效

指定模块申请动态内存

LOS_MemMalloc

从指定动态内存池分配内存给指定的模块,并纳入模块统计。

释放指定模块的一个内存块

LOS_MemMfree

释放已经申请的内存块,并纳入模块统计。

指定模块内存对齐分配

LOS_MemMallocAlign

从指定动态内存池中申请长度为size且地址按boundary字节对齐的内存,并纳入模块统计。

指定模块重新分配内存块

LOS_MemMrealloc

按size大小重新分配内存块,并保留原内存块内容,并纳入模块统计。

获取模块内存使用量

LOS_MemMusedGet

获取指定模块当前内存使用量。

显示空闲内存块的规模和数量

LOS_MemFreeNodeShow

显示指定内存池当前空闲内存块的规模及相对应的数量。

DLINK开发流程
  1. 配置:

    OS_SYS_MEM_ADDR:系统动态内存池起始地址,一般不需要修改。

    OS_SYS_MEM_SIZE:系统动态内存池大小,以byte为单位,系统默认分配DDR后未使用的空间。

    LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK:内存越界检测开关,默认关闭。打开后,每次申请动态内存时执行动态内存块越界检查。

  2. 初始化LOS_MemInit。

    初始一个内存池后如图,生成一个 EndNode,并且剩余的内存全部被标记为FreeNode节点。注:EndNode作为内存池末尾的节点,size为0。

    4.2 内存 - 图5

  3. 申请任意大小的动态内存LOS_MemAlloc。

    判断动态内存池中是否存在申请量大小的空间,若存在,则划出一块内存块,以指针形式返回,若不存在,返回NULL。

    调用三次LOS_MemAlloc函数可以创建三个节点,假设名称分别为UsedA,UsedB,UsedC,大小分别为sizeA,sizeB,sizeC。因为刚初始化内存池的时候只有一个大的FreeNode,所以这些内存块是从这个FreeNode中切割出来的。

    4.2 内存 - 图6

    当内存池中存在多个FreeNode的时候进行malloc,将会适配最合适大小的FreeNode用来新建内存块,减少内存碎片。若新建的内存块不等于被使用的FreeNode的大小,则在新建内存块后,多余的内存又会被标记为一个新的FreeNode。

  4. 释放动态内存LOS_MemFree。

    回收内存块,供下一次使用。

    假设调用LOS_MemFree释放内存块UsedB,则会回收内存块UsedB,并且将其标记为FreeNode。

    4.2 内存 - 图7

BEST LITTLE开发流程
  1. 配置:

    OS_SYS_MEM_ADDR:系统动态内存池起始地址,需要用户指定。

    OS_SYS_MEM_SIZE:系统动态内存池大小,以byte为单位,需要用户正确计算。

    LOSCFG_MEMORY_BESTFIT:置为YES,选择内存管理算法中的BESTFIT算法。

  2. 初始化:

    调用LOS_MemInit函数初始化用户指定的动态内存池。

  3. 申请任意大小的动态内存:

    调用LOS_MemAlloc函数从指定的内存池中申请指定大小的内存块,申请时内存管理向堆内存空间申请,最后将申请结果返回给用户。在向堆内存空间申请时,会存在内存块的切分。

  4. 释放动态内存:

    调用LOS_MemFree函数向指定的动态内存池释放指定的内存块,向堆内存空间释放内存块。在向堆内存空间释放时,会存在内存块的合并。

平台差异性

无。

注意事项

  • 由于系统中动态内存管理需要消耗管理控制块结构的内存,故实际用户可使用空间总量小于在配置文件los_config.h中配置项OS_SYS_MEM_SIZE的大小。

  • 系统中地址对齐申请内存分配LOS_MemAllocAlign可能会消耗部分对齐导致的空间,故存在一些内存碎片,当系统释放该对齐内存时,同时回收由于对齐导致的内存碎片。

  • 系统中重新分配内存LOS_MemRealloc函数如果分配成功,系统会自己判定是否需要释放原来申请的空间,返回重新分配的空间。如果重新分配失败,原来的空间保持不变,并返回NULL。禁止使用pPtr = LOS_MemRealloc(pool, pPtr, uwSize); 即:不能使用原本的pPtr变量直接去接收返回值。

  • 系统中多次调用LOS_MemFree时,第一次会返回成功,但对同一块内存进行多次重复释放会导致非法指针操作,导致结果不可预知。

  • 由于系统动态内存管理内存节点控制块结构体中,节点size数据类型为UINT32,高两位为标志位,因此用户初始化内存池的大小不能超过1G,否则会出现不可预知结果。

  • 分模块内存统计依赖于LOSCFG_MEM_MUL_MODULE,使用时需要在配置文件中定义此宏。

编程实例

实例描述

Huawei LiteOS运行期间,用户需要频繁的使用内存资源,而内存资源有限,必须确保将有限的内存资源分配给急需的程序,同时释放不用的内存。

通过Huawei LiteOS内存管理模块可以保证高效、正确的申请、释放内存。

本实例执行以下步骤:

  1. 初始化一个动态内存池。
  2. 在动态内存池中申请一个内存块。
  3. 使用这块内存块存放一个数据。
  4. 打印出存放在内存块中的数据。
  5. 释放掉这块内存。
编程实例
  1. UINT8 * m_aucSysMem_Tmp;
  2. VOID los_memory_test() {
  3. UINT32 *p_num = NULL;
  4. UINT32 uwRet;
  5. uwRet = LOS_MemInit(m_aucSysMem_Tmp, 32);
  6. if (LOS_OK == uwRet) {
  7. dprintf("内存池初始化成功!\n");
  8. }
  9. else {
  10. dprintf("内存池初始化失败!\n");
  11. return;
  12. }
  13. /*分配内存*/
  14. p_num = (int*)LOS_MemAlloc(m_aucSysMem_Tmp, 4);
  15. if (NULL == p_num) {
  16. dprintf("内存分配失败!\n");
  17. return;
  18. }
  19. dprintf("内存分配成功\n");
  20. /*赋值*/
  21. *p_num = 828;
  22. dprintf("*p_num = %d\n", *p_num);
  23. /*释放内存*/
  24. uwRet = LOS_MemFree(m_aucSysMem_Tmp, p_num);
  25. if (LOS_OK == uwRet) {
  26. dprintf("内存释放成功!\n");
  27. }
  28. else {
  29. dprintf("内存释放失败!\n");
  30. }
  31. return;
  32. }
结果验证

图 1 结果显示

4.2 内存 - 图8

完整实例代码

sample_mem.c

静态内存

开发指导

使用场景

当用户需要使用固定长度的内存时,可以使用静态内存分配的方式获取内存,一旦使用完毕,通过静态内存释放函数归还所占用内存,使之可以重复使用。

功能

Huawei LiteOS的静态内存管理主要为用户提供以下功能。

功能分类

接口名

描述

初始化静态内存

LOS_MemboxInit

初始化一个静态内存池,设定其起始地址、总大小及每个块大小。

清除静态内存内容

LOS_MemboxClr

清零静态内存块。

申请一块静态内存

LOS_MemboxAlloc

申请一块静态内存块。

释放内存

LOS_MemboxFree

释放一个静态内存块。

分析静态内存池状态

LOS_MemboxStatisticsGet

获取静态内存池的统计信息。

打印静态内存池内容

LOS_ShowBox

打印指定静态内存池所有节点信息(打印等级是LOS_INFO_LEVEL)

开发流程

本节介绍使用静态内存的典型场景开发流程。

  1. 规划一片内存区域作为静态内存池。

  2. 调用LOS_MemboxInit接口。

    系统内部将会初始化静态内存池。将入参指定的内存区域分割为N块(N值取决于静态内存总大小和块大小),将所有内存块挂到空闲链表,在内存起始处放置控制头。

  3. 调用LOS_MemboxAlloc接口。

    系统内部将会从空闲链表中获取第一个空闲块,并返回该块的用户空间地址。

  4. 调用LOS_MemboxFree接口。

    将该块内存加入空闲块链表。

  5. 调用LOS_MemboxClr接口。

    系统内部清零静态内存块,将入参地址对应的内存块清零。

平台差异性

无。

注意事项

  • 静态内存池区域,可以通过定义全局数组或调用动态内存分配接口方式获取。如果使用动态内存分配方式,在不需要静态内存池时,注意要释放该段内存,避免内存泄露。

编程实例

实例描述

Huawei LiteOS运行期间,用户需要频繁的使用内存资源,而内存资源有限,必须确保将有限的内存资源分配给急需的程序,同时释放不用的内存。

通过内存管理模块可以保证正确且高效的申请释放内存。

本实例执行以下步骤:

  1. 初始化一个静态内存池。
  2. 从静态内存池中申请一块静态内存。
  3. 使用这块内存块存放一个数据。
  4. 打印出存放在内存块中的数据。
  5. 清除内存块中的数据。
  6. 释放掉这块内存。
编程实例
  1. VOID los_membox_test(void) {
  2. UINT32 *p_num = NULL;
  3. UINT32 uwBlkSize = 10, uwBoxSize = 100;
  4. UINT32 uwRet;
  5. UINT32 pBoxMem[1000];
  6. uwRet = LOS_MemboxInit(&pBoxMem[0], uwBoxSize, uwBlkSize);
  7. if (uwRet != LOS_OK)
  8. {
  9. dprintf("内存池初始化失败!\n");
  10. return;
  11. }
  12. else {
  13. dprintf("内存池初始化成功!\n");
  14. }
  15. /*申请内存块*/
  16. p_num = (int*)LOS_MemboxAlloc(pBoxMem);
  17. if (NULL == p_num) {
  18. dprintf("内存分配失败!\n");
  19. return;
  20. }
  21. dprintf("内存分配成功\n");
  22. /*赋值*/
  23. *p_num = 828;
  24. dprintf("*p_num = %d\n", *p_num);
  25. /*清除内存内容*/
  26. LOS_MemboxClr(pBoxMem, p_num);
  27. dprintf("清除内存内容成功\n *p_num = %d\n", *p_num);
  28. /*释放内存*/
  29. uwRet = LOS_MemboxFree(pBoxMem, p_num);
  30. if (LOS_OK == uwRet) {
  31. dprintf("内存释放成功!\n");
  32. }
  33. else{
  34. dprintf("内存释放失败!\n");
  35. }
  36. return;
  37. }
结果验证

图 1 结果显示

4.2 内存 - 图9

完整实例代码

sample_membox.c