鸿蒙南向开发指南:轻量系统内核(LiteOS-M)内存管理 ...

打印 上一主题 下一主题

主题 561|帖子 561|积分 1683

根本概念

内存管理模块管理系统的内存资源,它是利用系统的核心模块之一,重要包罗内存的初始化、分配以及开释。
在系统运行过程中,内存管理模块通过对内存的申请/开释来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。
OpenHarmony LiteOS-M的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、开释等功能。


  • 动态内存:在动态内存池中分配用户指定巨细的内存块。

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

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

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

静态内存

运行机制

静态内存实质上是一个静态数组,静态内存池内的块巨细在初始化时设定,初始化后块巨细不可变更。
静态内存池由一个控制块LOS_MEMBOX_INFO和若干雷同巨细的内存块LOS_MEMBOX_NODE构成。控制块位于内存池头部,用于内存块管理,包罗内存块巨细uwBlkSize,内存块数量uwBlkNum,已分配使用的内存块数量uwBlkCnt和空闲内存块链表stFreeList。内存块的申请和开释以块巨细为粒度,每个内存块包罗指向下一个内存块的指针pstNext。
图1 静态内存示意图 

开发指导

使用场景

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

OpenHarmony LiteOS-M的静态内存管理重要为用户提供以下功能,接口具体信息可以检察API参考。
表1 静态内存模块接口
功能分类接口名初始化静态内存池LOS_MemboxInit:初始化一个静态内存池,根据入参设定其起始地点、总巨细及每个内存块巨细。扫除静态内存块内容LOS_MemboxClr:清零从静态内存池中申请的静态内存块的内容。申请、开释静态内存 LOS_MemboxAlloc:从指定的静态内存池中申请一块静态内存块。
 LOS_MemboxFree:开释从静态内存池中申请的一块静态内存块。获取、打印静态内存池信息  LOS_MemboxStatisticsGet:获取指定静态内存池的信息,包罗内存池中总内存块数量、已经分配出去的内存块数量、每个内存块的巨细。
 LOS_ShowBox:打印指定静态内存池全部节点信息,打印等级是LOG_INFO_LEVEL(当前打印等级设置是PRINT_LEVEL),包罗内存池起始地点、内存块巨细、总内存块数量、每个空闲内存块的起始地点、全部内存块的起始地点。

     阐明: 初始化后的内存池的内存块数量,不便是总巨细除于内存块巨细,因为内存池的控制块和每个内存块的控制头,都存在内存开销,设置总巨细时,需要将这些因素考虑进去。
  开发流程

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

  • 规划一片内存地区作为静态内存池。
  • 调用LOS_MemboxInit初始化静态内存池。 初始化会将入参指定的内存地区分割为N块(N值取决于静态内存总巨细和块巨细),将全部内存块挂到空闲链表,在内存起始处放置控制头。
  • 调用LOS_MemboxAlloc接口分配静态内存。 系统将会从空闲链表中获取第一个空闲块,并返回该内存块的起始地点。
  • 调用LOS_MemboxClr接口。 将入参地点对应的内存块清零。
  • 调用LOS_MemboxFree接口。 将该内存块加入空闲链表。
编程实例

本实例执行以下步骤:

  • 初始化一个静态内存池。
  • 从静态内存池中申请一块静态内存。
  • 在内存块存放一个数据。
  • 打印出内存块中的数据。
  • 扫除内存块中的数据。
  • 开释该内存块。 示例代码如下:
    本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleStaticMem。
  1. #include "los_membox.h"
  2. #define MEMBOX_POOL_SIZE    100
  3. #define MEMBOX_BLOCK_SZIE   10
  4. #define MEMBOX_WR_TEST_NUM  828
  5. VOID ExampleStaticMem(VOID)
  6. {
  7.     UINT32 *mem = NULL;
  8.     UINT32 blkSize = MEMBOX_BLOCK_SZIE;
  9.     UINT32 poolSize = MEMBOX_POOL_SIZE;
  10.     UINT32 boxMem[MEMBOX_POOL_SIZE];
  11.     UINT32 ret;
  12.     /* 内存池初始化 */
  13.     ret = LOS_MemboxInit(&boxMem[0], poolSize, blkSize);
  14.     if(ret != LOS_OK) {
  15.         printf("Membox init failed!\n");
  16.         return;
  17.     } else {
  18.         printf("Membox init success!\n");
  19.     }
  20.     /* 申请内存块 */
  21.     mem = (UINT32 *)LOS_MemboxAlloc(boxMem);
  22.     if (mem == NULL) {
  23.         printf("Mem alloc failed!\n");
  24.         return;
  25.     }
  26.     printf("Mem alloc success!\n");
  27.     /* 内存地址读写验证 */
  28.     *mem = MEMBOX_WR_TEST_NUM;
  29.     printf("*mem = %d\n", *mem);
  30.     /* 清除内存内容 */
  31.     LOS_MemboxClr(boxMem, mem);
  32.     printf("Mem clear success \n*mem = %d\n", *mem);
  33.     /* 释放内存 */
  34.     ret = LOS_MemboxFree(boxMem, mem);
  35.     if (LOS_OK == ret) {
  36.         printf("Mem free success!\n");
  37.     } else {
  38.         printf("Mem free failed!\n");
  39.     }
  40.     return;
  41. }
复制代码
结果验证

输出结果如下:
  1. Membox init success!
  2. Mem alloc success!
  3. *mem = 828
  4. Mem clear success   
  5. *mem = 0
  6. Mem free success!
复制代码
动态内存

运行机制

动态内存管理,即在内存资源充足的情况下,根据用户需求,从系统设置的一块比力大的连续内存(内存池,也是堆内存)中分配任意巨细的内存块。当用户不需要该内存块时,又可以开释回系统供下一次使用。与静态内存相比,动态内存管理的优点是按需分配,缺点是内存池中容易出现碎片。
OpenHarmony LiteOS-M动态内存在TLSF算法的底子上,对区间的划分进行了优化,得到更优的性能,降低了碎片率。动态内存核默算法框图如下:
图1 轻量系统动态内存核默算法 

根据空闲内存块的巨细,使用多个空闲链表来管理。根据内存空闲块巨细分为两个部分:[4, 127]和[27, 231],如上图size class所示:

  • 对[4,127]区间的内存进行等分,如上图下半部分所示,分为31个小区间,每个小区间对应内存块巨细为4字节的倍数。每个小区间对应一个空闲内存链表和用于标志对应空闲内存链表是否为空的一个比特位,值为1时,空闲链表非空。[4,127]区间的31个小区间内存对应31个比特位进行标志链表是否为空。
  • 大于127字节的空闲内存块,按照2的次幂区间巨细进行空闲链表管理。总共分为24个小区间,每个小区间又等分为8个二级小区间,见上图上半部分的Size Class和Size SubClass部分。每个二级小区间对应一个空闲链表和用于标志对应空闲内存链表是否为空的一个比特位。总共24*8=192个二级小区间,对应192个空闲链表和192个比特位进行标志链表是否为空。
例如,当有40字节的空闲内存需要插入空闲链表时,对应小区间[40,43],第10个空闲链表,位图标志的第10比特位。把40字节的空闲内存挂载第10个空闲链表上,并判定是否需要更新位图标志。当需要申请40字节的内存时,根据位图标志获取存在满足申请巨细的内存块的空闲链表,从空闲链表上获取空闲内存节点。假如分配的节点大于需要申请的内存巨细,进行分割节点利用,剩余的节点重新挂载到相应的空闲链表上。当有580字节的空闲内存需要插入空闲链表时,对应二级小区间[2^9,2^9+2^6],第31+2*8=47个空闲链表,并使用位图的第47个比特位来标志链表是否为空。把580字节的空闲内存挂载第47个空闲链表上,并判定是否需要更新位图标志。当需要申请580字节的内存时,根据位图标志获取存在满足申请巨细的内存块的空闲链表,从空闲链表上获取空闲内存节点。假如分配的节点大于需要申请的内存巨细,进行分割节点利用,剩余的节点重新挂载到相应的空闲链表上。假如对应的空闲链表为空,则向更大的内存区间去查询是否有满足条件的空闲链表,实际计算时,会一次性查找到满足申请巨细的空闲链表。
内存管理结构如下图所示:
图2 轻量系统动态内存管理结构图 



  • 内存池池头部分 内存池池头部分包罗内存池信息、位图标志数组和空闲链表数组。内存池信息包罗内存池起始地点及堆地区总巨细,内存池属性。位图标志数组有7个32位无符号整数构成,每个比特位标志对应的空闲链表是否挂载空闲内存块节点。空闲内存链表包罗223个空闲内存头节点信息,每个空闲内存头节点信息维护内存节颔首和空闲链表中的前驱、后继空闲内存节点。
  • 内存池节点部分 包罗3种范例节点:未使用空闲内存节点,已使用内存节点和尾节点。每个内存节点维护一个前序指针,指向内存池中上一个内存节点,还维护内存节点的巨细和使用标志。空闲内存节点和已使用内存节点后面的内存地区是数据域,尾节点没有数据域。
一些芯片片内RAM巨细无法满足要求,需要使用片外物理内存进行扩充。对于这样的多段非连续性内存, LiteOS-M内核支持把多个非连续性内存逻辑上合一,用户不感知底层的多段非连续性内存地区。 LiteOS-M内核内存模块把不连续的内存地区作为空闲内存结点插入到空闲内存节点链表,把差别内存地区间的不连续部分标志为假造的已使用内存节点,从逻辑上把多个非连续性内存地区实现为一个统一的内存池。下面通过示意图阐明下多段非连续性内存的运行机制:
图3 非连续性内存合一示意图 

联合上述示意图,非连续性内存合并为一个统一的内存池的步骤如下:

  • 把多段非连续性内存地区的第一块内存地区通过调用LOS_MemInit接口进行初始化。
  • 获取下一个内存地区的开始地点和长度,计算该内存地区和上一块内存地区的隔断巨细gapSize。
  • 把内存地区隔断部分视为假造的已使用节点,使用上一个内存地区的尾节点,设置其巨细为gapSize + OS_MEM_NODE_HEAD_SIZE(即sizeof(struct OsMemUsedNodeHead))。
  • 把当前内存地区划分为一个空闲内存节点和一个尾节点,把空闲内存节点插入到空闲链表,并设置各个节点的前后链接关系。
  • 假如有更多的非连续内存地区,重复上述步骤2-4。
开发指导

使用场景

动态内存管理的重要工作是动态分配并管理用户申请到的内存区间。动态内存管理重要用于用户需要使用巨细不等的内存块的场景,当用户需要使用内存时,可以通过利用系统的动态内存申请函数索取指定巨细的内存块,一旦使用完毕,通过动态内存开释函数归还所占用内存,使之可以重复使用。
接口阐明

OpenHarmony LiteOS-M的动态内存管理重要为用户提供以下功能,接口具体信息可以检察API参考。
表1 动态内存模块接口
功能分类接口描述初始化和删除内存池 LOS_MemInit:初始化一块指定的动态内存池,巨细为size。
 LOS_MemDeInit:删除指定内存池,仅打开编译控制开关LOSCFG_MEM_MUL_POOL时有效。申请、开释动态内存 LOS_MemAlloc:从指定动态内存池中申请size长度的内存。
 LOS_MemFree:开释从指定动态内存中申请的内存。
 LOS_MemRealloc:开释从指定动态内存中申请的内存。获取内存池信息 LOS_MemPoolSizeGet:获取指定动态内存池的总巨细。
 LOS_MemTotalUsedGet:获取指定动态内存池的总使用量巨细。
 LOS_MemInfoGet:获取指定内存池的内存结构信息,包罗空闲内存巨细、已使用内存巨细、空闲内存块数量、已使用的内存块数量、最大的空闲内存块巨细。
 LOS_MemPoolList:打印系统中已初始化的全部内存池,包罗内存池的起始地点、内存池巨细、空闲内存总巨细、已使用内存总巨细、最大的空闲内存块巨细、空闲内存块数量、已使用的内存块数量。仅打开编译控制开关LOSCFG_MEM_MUL_POOL时有效。获取内存块信息 LOS_MemFreeNodeShow:打印指定内存池的空闲内存块的巨细及数量。
 LOS_MemUsedNodeShow:打印指定内存池的已使用内存块的巨细及数量。查抄指定内存池的完整性LOS_MemIntegrityCheck:对指定内存池做完整性查抄,仅打开编译控制开关LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK时有效。增加非连续性内存地区LOS_MemRegionsAdd:支持多段非连续性内存地区,把非连续性内存地区逻辑上整合为一个统一的内存池。仅打开LOSCFG_MEM_MUL_REGIONS时有效。假如内存池指针参数pool为空,则使用多段内存的第一个初始化为内存池,其他内存地区,作为空闲节点插入;假如内存池指针参数pool不为空,则把多段内存作为空闲节点,插入到指定的内存池。    阐明:
  

  • 由于动态内存管理需要管理控制块数据结构来管理内存,这些数据结构会额外斲丧内存,故实际用户可使用内存总量小于设置项OS_SYS_MEM_SIZE的巨细。
  • 对齐分配内存接口LOS_MemAllocAlign/LOS_MemMallocAlign因为要进行地点对齐,大概会额外斲丧部分内存,故存在一些遗失内存,当系统开释该对齐内存时,同时接纳由于对齐导致的遗失内存。
  • 非连续性内存地区接口LOS_MemRegionsAdd的LosMemRegion数组参数传入的非连续性内存地区需要按各个内存地区的内存开始地点升序,且内存地区不能重叠。
  开发流程

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

  • 初始化LOS_MemInit。 初始一个内存池后天生一个内存池控制头、尾节点EndNode,剩余的内存被标志为FreeNode内存节点。注:EndNode作为内存池末了的节点,size为0。
  • 申请任意巨细的动态内存LOS_MemAlloc。 判定动态内存池中是否存在大于申请量巨细的空闲内存块空间,若存在,则划出一块内存块,以指针形式返回,若不存在,返回NULL。假如空闲内存块大于申请量,需要对内存块进行分割,剩余的部分作为空闲内存块挂载到空闲内存链表上。
  • 开释动态内存LOS_MemFree。 接纳内存块,供下一次使用。调用LOS_MemFree开释内存块,则会接纳内存块,并且将其标志为FreeNode。在接纳内存块时,相邻的FreeNode会自动合并。
编程实例

本实例执行以下步骤:

  • 初始化一个动态内存池。
  • 从动态内存池中申请一个内存块。
  • 在内存块中存放一个数据。
  • 打印出内存块中的数据。
  • 开释该内存块。
示例代码如下:
本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleDynMem。
  1. #include "los_memory.h"
  2. #define TEST_POOL_SIZE (2*1024)
  3. #define MEMBOX_WR_TEST_NUM  828
  4. __attribute__((aligned(4))) UINT8 g_testDynPool[TEST_POOL_SIZE];
  5. VOID ExampleDynMem(VOID)
  6. {
  7.     UINT32 *mem = NULL;
  8.     UINT32 ret;
  9.     /* 初始化内存池 */
  10.     ret = LOS_MemInit(g_testDynPool, TEST_POOL_SIZE);
  11.     if (LOS_OK  == ret) {
  12.         printf("Mem init success!\n");
  13.     } else {
  14.         printf("Mem init failed!\n");
  15.         return;
  16.     }
  17.     /* 申请内存块 */
  18.     mem = (UINT32 *)LOS_MemAlloc(g_testDynPool, 4);
  19.     if (mem == NULL) {
  20.         printf("Mem alloc failed!\n");
  21.         return;
  22.     }
  23.     printf("Mem alloc success!\n");
  24.     /* 内存地址读写验证 */
  25.     *mem = MEMBOX_WR_TEST_NUM;
  26.     printf("*mem = %d\n", *mem);
  27.     /* 释放内存 */
  28.     ret = LOS_MemFree(g_testDynPool, mem);
  29.     if (LOS_OK == ret) {
  30.         printf("Mem free success!\n");
  31.     } else {
  32.         printf("Mem free failed!\n");
  33.     }
  34.     return;
  35. }
复制代码
结果验证

输出结果如下:
  1. Mem init success!
  2. Mem alloc success!
  3. *mem = 828
  4. Mem free success!
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

吴旭华

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表