更多内容请访问:鸿蒙技术社区与华为官方共建https://harmonyos.51cto.com3.1.2.3函数OsVmPhysLargeAlloc当这个函数被执行,则意味着freelist上单个内存页节点的大小不能满足要求,超过了第9个链表上的内存页节点的大小。(1)计算需要申请的内存大小。(2)从最大链表遍历每个内存页节点。⑶根据每个内存页的起始内存地址,计算出所需内存的结束地址,如果超过内存段的大小,则继续遍历下一个内存页节点。⑷此时paStart表示当前内存页的结束地址,则paStart>=paEnd表示当前内存页的大小满足应用要求;paStartstartandpaStart>=(seg->start+seg->size)发生溢出错误,内存页的结束地址不在内存段的地址范围内。⑸表示当前内存页的下一个内存页结构。如果该结构不在空闲列表中,则break跳出循环。如果在freelist上,则意味着将连续的free内存页拼接在一起,以满足大内存应用的需要。⑩表示一个或多个连续内存页的大小满足应用要求。STATICLosVmPage*OsVmPhysLargeAlloc(structVmPhysSeg*seg,size_tnPages){structVmFreeList*list=NULL;LosVmPage*page=NULL;LosVmPage*tmp=NULL;PADDR_TpaStart;PADDR_TpaEnd;⑴size_tsize=nPages<1freeListDER[];LOS_DL_LIST_FOR_EACH_ENTRY(page,&list->node,LosVmPage,node){⑶paStart=page->physAddr;paEnd=paStart+size;if(paEnd>(seg->start+seg->size)){continue;}for(;;){⑷paStart+=PAGE_SIZE<<(VM_LIST_ORDER_MAX-1);if((paStart>=paEnd)||(paStartstart)||(paStart>=(seg->start+seg->size))){break;}⑸tmp=&seg->pageBase[(paStart-seg->start)>>PAGE_SHIFT];if(tmp->order!=(VM_LIST_ORDER_MAX-1)){break;}}⑹if(paStart>=paEnd){returnpage;}}returnNULL;}3.1.2.4函数OsVmPhysFreeListDelUnsafe和OsVmPhysFreeListAddUnsafe内部函数OsVmPhysFreeListDelUnsafe用于从空闲内存页节点链表中删除一个内存页节点。名字中有Unsafe二字,是因为函数体中没有链表操作,加上自旋锁,安全性由外部调用函数来保证。(1)到位检查,确保内存段和freelist的索引符合要求。(2)获取内存段和空闲链表,(3)将空闲链表上的内存页节点数减1,将内存块从空闲链表中删除。(4)将内存页的顺序索引值设置为最大值,标记为非空闲内存页。STATICVOIDOsVmPhysFreeListDelUnsafe(LosVmPage*page){structVmPhysSeg*seg=NULL;structVmFreeList*list=NULL;⑴if((page->segID>=VM_PHYS_SEG_MAX)||(page->order>=VM_LIST_ORDER_MAX)){LOS_Panic("Thepagesegmentid(%)ororder(%u)isinvalid\n",page->segID,page->order);}⑵seg=&g_vmPhysSeg[page->segID];list=&seg->freeList[page->order];⑶list->listCnt--;LOS_ListDelete(&page->node);⑷page->order=VM_LIST_ORDER_MAX;}空闲链表上删除对应的函数是函数OsVmPhysFreeListAddUnsafe,在空闲链表上插入一个空闲内存页节点。(1)更新待挂载内存页的空闲链表的索引值,然后获取内存页所在的内存段seg,获取索引值对应的空闲链表。执行(2)将空闲内存页节点插入空闲链表,更新节点数。STATICVOIDOsVmPhysFreeListAddUnsafe(LosVmPage*page,UINT8order){structVmPhysSeg*seg=NULL;structVmFreeList*list=NULL;if(page->segID>=VM_PHYS_SEG_MAX){LOS_Panic("Thepagesegmentid(%d)isinvalid\n",page->segID);}⑴page->order=order;seg=&g_vmPhysSeg[page->segID];list=&seg->freeList[order];⑵LOS_ListTailInsert(&list->node,&page->node);list->listCnt++;}3.1.2.5函数OsVmPhysPagesSpiltUnsafe函数OsVmPhysPagesSpiltUnsafe用于内存块的分割。参数中的oldOrder表示申请的内存页节点对应的链表索引,newOrder表示实际申请的内存页节点对应的链表索引。如果索引值相等,则不需要拆分,不会执行for循环块的代码。由于buddy算法中链表数组中元素的特点,即每个链表中内存页节点的大小等于内存页的2次方。拆分时,从索引高的newOrder依次遍历到索引低的oldOrder,拆分出一个内存页节点作为空闲内存页节点挂载到对应的空闲链表上。⑴从高索引开始循环到低索引,索引值减1,然后执行⑵获取伙伴内存页节点,可以看出,当请求的内存块大于需求时,高地址部分的后半部分会被放入空闲链表,保留前半部分的低地址部分。(3)处的断言确保伙伴内存页节点索引值为最大值,表明它属于一个空闲内存页节点。(4)调用函数将内存页节点放入空闲链表。STATICVOIDOsVmPhysPagesSpiltUnsafe(LosVmPage*page,UINT8oldOrder,UINT8newOrder){UINT32order;LosVmPage*buddyPage=NULL;for(order=newOrder;order>oldOrder;){⑴order--;⑵buddyPage=&page[VM_ORDER_TO_PAGES-RTSE(order)];(GULOSorder==VM_LIST_ORDER_MAX);⑷OsVmPhysFreeListAddUnsafe(buddyPage,order);}}为了直观演示,有必要放这张图。假设我们需要申请8个内存页大小的内存节点,但是freeList[7]链表上只有空闲节点可用。申请成功后,超过申请要求的大小,需要拆分。将27个内存页分成2个节点,大小为26个内存页,第一个继续拆分,第二个挂载到freeList[6]链表上。然后将前26个内存页拆分为两个25个内存页节点,继续拆分第一份,将第二份挂载到freeList[5]链表中。依次进行,最后分裂成两个内存页节点,大小为2^3个内存页。第一份作为实际请求的内存页返回,第二份挂载到freeList[3]链表上。如下图红色部分所示。另外,函数OsVmRecycleExtraPages会调用OsVmPhysPagesFreeContiguous来回收请求的额外内存页,后面会分析。3.2释放物理内存页接口3.2.1释放物理内存页接口介绍对应于申请的物理内存页接口,有3个释放物??理内存页的接口,用于满足不同的释放内存页需求。函数LOS_PhysPagesFreeContiguous的传入参数是虚拟内存地址和要释放的物理页对应的内核虚拟地址空间中的内存页数。(1)调用函数OsVmVaddrToPage将虚拟内存地址转换为物理内存页结构的地址,然后(2)将内存页的连续内存页数设置为0。⑶调用函数OsVmPhysPagesFreeContiguous()将释放物理内存页。函数LOS_PhysPageFree用于释放一个物理内存页,传入的参数是要释放的物理页对应的物理页结构的地址。(4)引用计数递减,当小于等于0时,表示在进一步释放操作之前没有其他引用。该函数还会调用函数OsVmPhysPagesFreeContiguous()释放物理内存页。LOS_PhysPagesFree函数用于释放挂在双向链表上的多个物理内存页,返回值为实际释放的物理页数。(5)遍历内存页双向链表,从链表中取出需要释放的内存页节点。⑴处的代码与释放一个内存页的函数代码相同。(7)计算遍历的内存页数,函数最后会返回这个值。VOIDLOS_PhysPagesFreeContiguous(VOID*ptr,size_tnPages){UINT32intSave;structVmPhysSeg*seg=NULL;LosVmPage*page=NULL;if(ptr==NULL){return;}⑴page=OsVmVaddrToPage(ptr);if(page==NULL){VM_ERR("vmpageofptr(%#x)isnull",ptr);return;}⑵page->nPages=0;seg=&g_vmPhysSeg[page->segID];LOS_SpinLockSave(&seg->freeListLock,&intSave);⑶OsVmPhysPagesFreeContiguous(page,nPages);LOS_SpinUnlockRestore(&seg->freeListLock,intSave);}......VOIDLOS_PhysPageFree(LosVmPage*page){UINT32intSave;structVmPhysSeg*seg=NULL;if(page==NULL){return;}⑷if(LOS_AtomicDecRet(&page->refCounts)<=0){seg=&g_vmPhysSeg[page->segID];LOS_SpinLockSave(&seg->freeListLock,&intSave);OsVmPhysPagesFreeContiguous(page,ONE_PAGE);LOS_AtomicSet(&page->refCounts,0);LOS_SpinUnlockRestore(&seg->freeListLock,intSave);}}·····size_tLOS_PhysPagesFree(LOS_DL_LIST*list){UINT32intSave;LosVmPage*page=NULL;LosVmPage*nPage=NULL;LosVmPhysSeg*seg=NULL;size_tcount=0;if(list==NULL){return0;}LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(page,nPage,list,LosVmPage,node){⑸LOS_ListDelete(&page->node);⑩if(LOS_AtomicDecRet(&page->refCounts)<=0){seg=&g_vmPhysSeg[page->segID];LOS_SpinLockSave(&seg->freeListLock,&intSave);OsVmPhysPagesFreeContiguous(page,ONE_PAGE);LOS_AtomicSet(&page->refCounts,0);LOS_SpinUnlockRestore(&seg->freeListLock,intSave);}⑺count++;}returncount;}3.2.2释放物理内存页内部接口实现3.2.2.1函数OsVmVaddrToPage函数OsVmVmVaddrToPage将虚拟内存地址转换为物理页结构地址⑴调用函数LOS_PaddrQuery()将虚拟地址转换为物理地址。该功能将在虚实映射部分详细介绍。(2)遍历物理内存段,如果物理内存地址在物理内存段的地址范围内,则可以返回该物理地址对应的物理页结构的地址。LosVmPage*OsVmVaddrToPage(VOID*ptr){structVmPhysSeg*seg=NULL;⑴PADDR_Tpa=LOS_PaddrQuery(ptr);UINT32segID;for(segID=0;segID=seg->start)&&(pa<(seg->start+seg->size))){returnseg->pageBase+((pa-seg->start)>>PAGE_SHIFT);}}returnNULL;}3.2.2.2函数OsVmPhysPagesFreeContiguous函数OsVmPhysPagesFreeContiguous()用于释放指定数量的连续物理内存页。(1)根据物理内存页获取对应的物理内存地址。(2)根据物理内存地址获取空闲内存页链表数组的索引值。(3)获取索引值对应的链表上内存页节点的内存页号。如果⑷处要释放的内存页数nPages小于当前链表的内存页节点数,则跳出循环,执行⑩处的代码,释放到小索引的双向链表。(5)调用函数OsVmPhysPagesFree()释放指定链表上的内存页,然后更新内存页数和内存页结构地址。(6)根据内存页数计算对应的链表索引,根据索引值计算内存页节点在链表上的大小。(7)调用函数OsVmPhysPagesFree()释放指定链表上的内存页,然后更新内存页数和内存页结构地址。VOIDOsVmPhysPagesFreeContiguous(LosVmPage*page,size_tnPages){paddr_tpa;UINT32order;size_tn;while(TRUE){⑴pa=VM_PAGE_TO_PHYS(page);⑵order=VM_PHYS_TO_ORDER(pa);⑶n=VM_ORDER_TO_PAGES(ordernPages);⑷age(br)PagesV}⑸age(br)Pagesmorder);nPages-=n;page+=n;}while(nPages>0){order=LOS_HighBitGet(nPages);n=VM_ORDER_TO_PAGES(order);⑺OsVmPhysPagesFree(page,order);nPages-=n;page+=n;}}3.2.2.3函数OsVmPhysPagesFree函数OsVmPhysPagesFree()释放内存页到对应的空闲内存页链表。当内存页块被释放时,会在当前链表中寻找地址连续的伙伴内存页块进行合并,然后再到上一级链表继续查找是否有连续的伙伴内存页块。⑴检查传入参数。(2)至少需要是倒数第二个链表,这样内存页节点才能和大索引链表上的节点合并。(3)获取内存页对应的物理内存地址,后面再启动do-while循环,查找是否有连续的内存页节点。(4)VM_ORDER_TO_PHYS(order)计算出链表索引值对应的伙伴位图,然后进行异或运算,计算出伙伴内存页的物理内存地址。(5)将物理地址转换为内存页结构,进一步判断:如果内存页不存在或者不在空闲列表中,则跳出while循环。否则,如果伙伴内存节点存在,则执行(6)将伙伴页从链表中移除,然后将索引值加1。(7)链表索引加1,然后进行逻辑运算,得到物理内存地址。此时物理内存地址与合并后的两个内存页块的地址是连续的。该内存地址不一定存在于更高级的空闲链表上。如果存在则继续合并,如果不存在则退出循环。当索引顺序为8,要插入到最后一个链表中,或者找不到可以合并的节点时,直接执行⑻,将内存页节点插入到空闲链表中。VOIDOsVmPhysPagesFree(LosVmPage*page,UINT8order){paddr_tpa;LosVmPage*buddyPage=NULL;⑴if((page==NULL)||(order>=VM_LIST_ORDER_MAX)){return;}⑵if(ordersegID);if((buddyPage==NULL)||(buddyPage->order!=order)){break;}OsVmPhysFreeListDel(buddyPage);order++;⑺pa&=~(VM_ORDER_TO_PHYS(order)-1);page=OsVmPhysToPage(pa,page->segID);}while(order=VM_PHYS_SEG_MAX){LOS_Panic("Thepagesegmentid(%d)isinvalid\n",segID);}seg=&g_vmPhysSeg[ifID](1);((pastart)||(pa>=(seg->start+seg->size))){returnNULL;}⑵offset=pa-seg->start;⑶return(seg->pageBase+(offset>>PAGE_SHIFT));}3.3.2LOS_PaddrToKVaddr函数LOS_PaddrToKVaddr函数根据物理地址获取对应的内核虚拟地址。在⑴处遍历物理内存段数组,然后在⑵处判断物理地址是否在遍历到的物理内存段的地址范围内,则执行⑶,传入的物理内存地址相对于物理起始地址的偏移量内存加内核态虚拟地址空间的起始地址是物理地址对应的内核虚拟地址。VADDR_T*LOS_PaddrToKVaddr(PADDR_Tpaddr){structVmPhysSeg*seg=NULL;UINT32segID;for(segID=0;segID=seg->start)&&(paddr<(seg->start+seg->size))){⑶return(VADDR_T*)(UINTPTR)(paddr-SYS_MEM_BASE+KERNEL_ASPACE_BASE);}}return(VADDR_T*)(UINTPTR)(paddr-SYS_MEM_BASE+KERNEL_ASPACE_BASE);}3.4其他函数3.4.1函数OsPhysSharePageCopy函数OsPhysSharePageCopy用于复制共享内存页。⑴进行参数校验,⑵获取旧内存页,⑶获取内存段。(4)如果旧内存页的引用计数为1,则直接将旧物理内存地址赋值给新物理内存地址。(5)如果内存页被多次引用,则先转换成虚拟内存地址,然后执行(6)复制内存页的内容。(7)刷新新旧内存页的引用计数。VOIDOsPhysSharePageCopy(PADDR_ToldPaddr,PADDR_T*newPaddr,LosVmPage*newPage){UINT32intSave;LosVmPage*oldPage=NULL;VOID*newMem=NULL;VOID*oldMem=NULL;LosVmPhysSeg*seg=NULL;oneif((newPage=(=NULL)||newPaddr==NULL)){VM_ERR("newPageinvalid");return;}oldPage=LOS_VmPageGet(oldPaddr);if(oldPage==NULL){VM_ERR("invalidoldPaddr%p",oldPaddr);return;}1seg=&g_vmPhysSeg[oldPage->segID];LOS_SpinLockSave(&seg->freeListLock,&intSave);9if(LOS_AtomicRead(&oldPage->refCounts)==1){*newPaddr=oldPaddr;}else{sevenMem=LOS_PaddrToKVaddr(*newPaddr);oldMem=LOS_Paddr(ToKVaddr);oldPaddr);if((newMem==NULL)||(oldMem==NULL)){LOS_SpinUnlockRestore(&seg->freeListLock,intSave);返回;}9if(memcpy_s(newMem,PAGE_SIZE,oldMem,PAGE_SIZE)!=EOK);{VM_ERR("memcpy_failed");}2LOS_AtomicInc(&newPage->refCounts);LOS_AtomicDec(&oldPage->refCounts);}LOS_SpinUnlockRestore(&seg->freeListLock,intSave);返回;structure,然后阅读了物理内存是如何初始化的,然后分析了物理内存的申请、释放、查询等操作接口的源码。/harmonyos.51cto.com