当前位置: 首页 > 科技观察

鸿蒙LightKernelMCore源码分析系列之八静态内存MemoryBox

时间:2023-03-20 13:09:46 科技观察

更多信息请访问:鸿蒙科技社区https://harmonyos.51cto.com,一个管理系统内存资源的内存管理模块,它是其中之一操作系统的核心模块,主要包括内存的初始化、分配和释放。在系统运行过程中,内存管理模块通过内存的申请/释放来管理用户和OS对内存的使用,从而优化内存的利用率和效率,同时解决系统的内存碎片问题,最大程度。鸿蒙LightKernel的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。动态内存:在动态内存池中分配用户指定大小的内存块。优点:按需分发。缺点:内存池可能产生碎片。静态内存:在静态内存池中分配用户初始化时预先设定(固定)大小的内存块。优点:分配和释放效率高,静态内存池无碎片。缺点:只能申请一个初始预设大小的内存块,不能按需申请。本文主要分析鸿蒙LightKernel的静态内存(MemoryBox),后续系列会继续分析动态内存。静态内存本质上是一个静态数组。静态内存池中的块大小是在初始化时设置的,初始化后不能更改块大小。一个静态内存池由一个控制块和几个相同大小的内存块组成。控制块位于内存池的头部,用于内存块管理。内存块的申请和释放以块大小为粒度。本文通过分析静态内存模块的源码帮助读者掌握静态内存的使用。接下来我们看一下静态内存的结构,静态内存的初始化,以及静态内存常用操作的源码。1.静态内存结构定义和公共宏定义1.1静态内存结构定义静态内存结构定义在文件kernel\include\los_membox.h中。源码如下,⑴定义静态内存节点LOS_MEMBOX_NODE结构体,⑵定义静态内存结构池信息结构体为LOS_MEMBOX_INFO,结构体成员解释见评论区。⑴typedefstructtagMEMBOX_NODE{structtagMEMBOX_NODE*pstNext;/**<静态内存池中的空闲节点指针,指向下一个空闲节点*/}LOS_MEMBOX_NODE;⑵typedefstructLOS_MEMBOX_INFO{UINT32uwBlkSize;/**<静态内存池中的空闲节点指针,指向到下一个空闲节点*/UINT32uwBlkNum;/**<静态内存池内存块总数*/UINT32uwBlkCnt;/**<静态内存池分配内存块总数*/#if(LOSCFG_PLATFORM_EXC==1)structLOS_MEMBOX_INFO*nextMemBox;/**<指向下一个静态内存池*/#endifLOS_MEMBOX_NODEstFreeList;/**<静态内存池中空闲内存块的单向链表*/}LOS_MEMBOX_INFO;下图说明了静态内存的使用。对于一个静态内存区,头部是LOS_MEMBOX_INFO信息,后面是每个内存块,每个内存块的大小是uwBlkSize,包括内存块节点LOS_MEMBOX_NODE和内存块数据区。空闲内存块节点指向下一个空闲内存块节点。1.2静态内存常用宏定义静态内存头文件中也提供了一些重要的宏定义。(1)LOS_MEMBOX_ALIGNED(memAddr)用于对齐内存地址,(2)OS_MEMBOX_NEXT(addr,blkSize)根据当前节点内存地址addr和内存块大小blkSize获取下一个内存块的内存地址。(3)OS_MEMBOX_NODE_HEAD_SIZE表示内存块中节点头的大小,每个内存块包括内存节点LOS_MEMBOX_NODE和存放业务的数据区。⑷表示静态内存的总大小,包括内存池信息结构占用的大小,以及每个内存块占用的大小。⑴#defineLOS_MEMBOX_ALIGNED(memAddr)(((UINTPTR)(memAddr)+sizeof(UINTPTR)-1)&(~(sizeof(UINTPTR)-1)))⑵#defineOS_MEMBOX_NEXT(addr,blkSize)(LOS_MEMBOX_NODE*)(VOID*)((UINT8*)(addr)+(blkSize))⑶#defineOS_MEMBOX_NODE_HEAD_SIZEsizeof(LOS_MEMBOX_NODE)⑷#defineLOS_MEMBOX_SIZE(blkSize,blkNum)\(sizeof(LOS_MEMBOX_INFO)+(LOS_MEMBOX_ALIGNED((blkSize)+OS_MEMBOX_NODE)*(HE)一些宏和内联函数也在文件kernel\src\mm\los_membox.c中定义。⑴定义了OS_MEMBOX_MAGIC魔字,这个32位魔字的后8位维护任务号信息,任务号位由⑵处的宏定义。⑶处的宏定义了任务号的最大值,⑷处的宏从魔字中提取任务号信息。(5)内联函数设置魔法字。从静态内存池分配内存块节点后,节点指针.pstNext不再指向下一个空闲内存块节点,而是设置为magicword。⑴处的内联函数是用来验证幻字的。⑺处的宏根据内存块的节点地址获取内存块的数据区地址,⑻处的宏根据内存块的数据区地址获取内存块的节点地址。⑴#defineOS_MEMBOX_MAGIC0xa55a5a00⑵#defineOS_MEMBOX_TASKID_BITS8⑶#defineOS_MEMBOX_MAX_TASKID((1<pstNext=(LOS_MEMBOX_NODE*)(OS_MEMBOX_MAGIC|taskID);}⑹STATICINLINEUINT32OsMemBoxCheckMagic(LOS_MEMBOX_NODE*node){UINT32taskID=OS_MEMBOX_TASKID_GET(node->pstNext);if(taskID>(LOSCFG_BASE_NORE_TSK_LIMIT+1)){return;(node->pstNext==(LOS_MEMBOX_NODE*)(OS_MEMBOX_MAGIC|taskID))?LOS_OK:LOS_NOK;}}⑺#defineOS_MEMBOX_USER_ADDR(addr)\((VOID*)((UINT8*)(addr)+OS_MEMBOX_NODE_HEAD_SIZE))⑻#defineOS_MEMBOX_NODE_ADDR(addr)\((LOS_MEMBOX_NODE*)(VOID*)((UINT8*)(addr)-OS_MEMBOX_NODE_HEAD_SIZE))2.静态内存的常用操作当用户需要使用定长内存时,可以通过静态内存获取allocation一旦内存用完,通过静态内存释放函数将占用的内存归还,以便重新使用。2.1初始化静态内存池下面分析一下初始化静态内存池函数UINT32LOS_MemboxInit(VOID*pool,UINT32poolSize,UINT32blkSize)的代码。先看函数参数,VOID*pool是静态内存池的起始地址,UINT32poolSize是初始化静态内存池的总大小,poolSize需要小于等于所在内存区域的大小*pool启动,否则会影响后续的内存区域。它还需要大于静态内存头大小sizeof(LOS_MEMBOX_INFO)。LengthUINT32blkSize是静态内存池中每个内存块的块大小。我们看代码,验证(1)处的传入参数。(2)设置静态内存池中每个内存块的实际大小,已经内存对齐,同时统计内存块中的节点信息。(3)计算内存池中的内存块总数,然后将已使用内存块数.uwBlkCnt设置为0。(4)如果可用内存块为0,则返回初始化失败。(5)获取内存池中第一个空闲的内存块节点。⑵将空闲内存块挂载到静态内存池信息结构的空闲内存块链表stFreeList.pstNext上,然后执行⑺每个空闲内存块依次指向下一个空闲内存块,将它们串联起来。UINT32LOS_MemboxInit(VOID*pool,UINT32poolSize,UINT32blkSize){LOS_MEMBOX_INFO*boxInfo=(LOS_MEMBOX_INFO*)pool;LOS_MEMBOX_NODE*node=NULL;UINT32index;UINT32intSave;⑴if(pool==NULL){returnLOS_NOK;}if(blkSize==0){returnLOS_NOK;}if(poolSizeuwBlkSize=LOS_MEMBOX_ALIGNED(blkSize+OS_MEMBOX_NODE_HEAD_SIZE);if(boxInfo->uwBlkSize==0){MEMBOX_UNLOCK(int_Save);return;}⑶boxInfo->uwBlkNum=(poolSize-sizeof(LOS_MEMBOX_INFO))/boxInfo->uwBlkSize;boxInfo->uwBlkCnt=0;⑷if(boxInfo->uwBlkNum==0){MEMBOX_UNLOCK(intSave);returnLOS_NOK;}⑸node=(LOS_MEMBOX_NODE*)(boxInfo+1);⑹boxInfo->stFreeList.pstNext=node;⑺for(index=0;indexuwBlkNum-1;++index){node->pstNext=OS_MEMBOX_NEXT(node,boxInfo->uwBlkSize);node=node->pstNext;}node->pstNext=NULL;#if(LOSCFG_PLATFORM_EXC==1)OsMemBoxAdd(pool);#endifMEMBOX_UNLOCK(intSave);returnLOS_OK;}2.2清理静态内存块我们可以使用函数VOIDLOS_MemboxClr(VOID*pool,VOID*box)清除静态内存块中数据区的内容需要2个参数,VOID*pool是初始化静态内存池的地址VOID*box是静态内存块的数据whosecontentneedstobecleared注意这里不是内存块的节点地址,每个内存块的节点区域不能被清除。下面分析源码。⑴验证参数,⑵调用memset_s()函数在内存块的数据区写入0。写入起始地址为内存块数据区的起始地址VOID*box,写入长度为数据区boxInfo->uwBlkSize-OS_MEMBOX_NODE_HEAD_SIZE的长度。VOIDLOS_MemboxClr(VOID*pool,VOID*box){LOS_MEMBOX_INFO*boxInfo=(LOS_MEMBOX_INFO*)pool;⑴if((pool==NULL)||(box==NULL)){return;}⑵(VOID)memset_s(box,(boxInfo->uwBlkSize-OS_MEMBOX_NODE_HEAD_SIZE),0,(boxInfo->uwBlkSize-OS_MEMBOX_NODE_HEAD_SIZE));}2.3申请和释放静态内存初始化静态内存池后,我们可以使用函数VOID*LOS_MemboxAlloc(VOID*pool)申请对于静态内存,分析下面的源码。(1)获取静态内存池空闲内存块链表的头节点。如果链表不为空,则执行(2),将下一个可用节点赋值给nodeTmp。在⑶处,执行链表的头节点到下一个链表节点,然后执行⑷为分配的内存块设置magicword,然后将内存池中已使用的内存块数加1。(5)返回时调用宏OS_MEMBOX_USER_ADDR()计算内存块的数据区地质。VOID*LOS_MemboxAlloc(VOID*pool){LOS_MEMBOX_INFO*boxInfo=(LOS_MEMBOX_INFO*)pool;LOS_MEMBOX_NODE*node=NULL;LOS_MEMBOX_NODE*nodeTmp=NULL;UINT32intSave;if(pool==NULL){returnNULL;}MEMBOX_LOCK(intSave)=&(boxInfo->stFreeList);if(node->pstNext!=NULL){⑵nodeTmp=node->pstNext;⑶node->pstNext=nodeTmp->pstNext;⑷OsMemBoxSetMagic(nodeTmp);boxInfo->uwBlkCnt++;}MEMBOX_UNLOCK(intSave);⑧return(nodeTmp==NULL)?NULL:OS_MEMBOX_USER_ADDR(nodeTmp);}使用完请求的内存块后,我们可以使用函数UINT32LOS_MemboxFree(VOID*pool,VOID*box)释放静态内存,需要2个参数,VOID*pool是初始化静态内存池的地址。VOID*box是需要释放的静态内存块的数据区起始地址。请注意,这不是内存块的节点地址。下面分析源码。(1)根据待释放内存块的数据区地址获取节点地址node,(2)先验证待释放内存块。在⑶处将要释放的内存块挂在内存池的空闲内存块链表上,然后执行⑷使已使用量减1。LITE_OS_SEC_TEXTUINT32LOS_MemboxFree(VOID*pool,VOID*box){LOS_MEMBOX_INFO*boxInfo=(LOS_MEMBOX_INFO*)pool;UINT32ret=LOS_NOK;UINT32intSave;if((pool==NULL)||(box==NULL)){returnLOS_NOK;}MEMBOX_intSave);do{⑴LOS_MEMBOX_NODE*node=OS_MEMBOX_NODE_ADDR(box);⑵if(OsCheckBoxMem(boxInfo,node)!=LOS_OK){break;}⑶node->pstNext=boxInfo->stFreeList.pstNext;boxInfo->stFreeList.pstNext=node;⑷boxInfo->uwBlkCnt--;ret=LOS_OK;}while(0);MEMBOX_UNLOCK(intSave);returnret;}接下来我们看OsCheckBoxMem()的校验函数。⑴如果内存池的块大小为0,返回验证失败。(2)计算要释放的快内存节点相对于第一个内存块节点的偏移量。(3)如果偏移量除以内存块数的余数不为0,则返回校验失败。⑷如果偏移量除以内存块数的商大于等于内存块数,则返回校验失败。(5)调用宏OsMemBoxCheckMagic对magicword进行校验。STATICINLINEUINT32OsCheckBoxMem(constLOS_MEMBOX_INFO*boxInfo,constVOID*node){UINT32offset;⑴if(boxInfo->uwBlkSize==0){returnLOS_NOK;}⑵offset=(UINT32)((UINTPTR)node-(UINTPTR)(boxInfo+1));⑴if((offset%boxInfo->uwBlkSize)!=0){returnLOS_NOK;}⑷if((offset/boxInfo->uwBlkSize)>=boxInfo->uwBlkNum){returnLOS_NOK;}⑸returnOsMemBoxCheckMagic((LOS_MEMBOX_NODE*)node);}我们一起分析了鸿蒙LightKernel静态内存模块的源码,包括静态内存结构、静态内存池初始化、静态内存申请、释放、清除等内容,后续会推出更多分享文章,敬请期待。更多信息请访问:Harmonyos.51cto.com,与华为官方合作打造的鸿蒙技术社区