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

HarmonyosLightKernelA核心源码解析系列的虚拟实景映射(三)虚拟物理内存映射

时间:2023-03-15 19:18:54 科技观察

更多内容请访问:Harmonyos.51cto.com,与华为官方共建的鸿蒙技术社区3、虚拟-真正的映射函数LOS_ArchMmuMap由上可见。当用户程序加载并启动时,代码段和数据段会被映射到虚拟内存空间中。此时,没有实际映射的物理页面;程序执行时,如下图(图片来自OpenHarmonydocs开源站点)粗箭头所示,CPU访问虚拟地址,通过MMU检查是否有对应的物理内存。如果虚拟地址没有对应的物理地址,则触发缺页异常,内核申请物理内存,并将虚实关系映射属性配置信息写入页表,页表项都缓存到TLB中,然后CPU可以通过转换关系直接访问实际的物理内存;如果CPU访问已经缓存到TLB的页表条目,则无需访问存储在内存中的页表以进行更快的查找。本节我们将详细分析虚实映射函数的实现代码。3.1函数LOS_ArchMmuMapLOS_ArchMmuMap函数用于映射进程空间的虚拟地址范围和物理地址范围,其中输入参数archMmu为MMU结构体,vaddr和paddr分别为虚拟内存和物理内存的起始地址;count是虚拟地址和物理地址映射的内存页数;flags是映射标签。⑴进行函数参数校验,不支持NON-SECURE标志。虚拟地址和物理地址需要在内存页的4KiB处对齐。参数查看功能代码简单,大家可以自行查看。(2)当虚拟地址和物理地址按1MiB对齐,且计数大于256时,使用Section页表项格式。⑶生成并保存L1段类型页表项,下面详细分析函数OsMapSection()。如果不满足(2)中的条件,则需要使用L2映射。先执行第4步获取虚拟地址vaddr对应的L1页表项,然后执行第5步判断是否映射,如果没有对应的映射则执行第6步的函数OsMapL1PTE生成并保存L1页table类型页表项,然后执行函数OsMapL2PageContinous生成并保存L2页表项。如果已经映射到L1页表项类型,则执行函数OsMapL2PageContinous生成L2页表项并保存。如果不是支持的页表项类型,执行LOS_Panic()触发异常。(7)统计生成映射调试,最后返回映射成功的次数。可以看出,在给出了虚拟和真实内存地址以及映射的内存页数之后,判断是使用L1页表映射还是L2页表映射的标准是:虚拟和真实内存地址是否为1MiB内存aligned,映射个数是否大于256。使用L1映射时,映射到Section页表项类型。当使用L2映射时,根据L1页表项的类型,分配处理无效页表项和PageTable页表项类型两种情况。具体映射方法见下文。status_tLOS_ArchMmuMap(LosArchMmu*archMmu,VADDR_Tvaddr,PADDR_Tpaddr,size_tcount,UINT32flags){PTE_Tl1Entry;UINT32saveCounts=0;INT32mapped=0;INT32checkRst;⑴checkRst=OsMapParamCheck(flags,vaddr,paddr);if(checkRstcheck<0);}/returnseewhatkindofmappingwecanuse*/while(count>0){⑵if(MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(vaddr)&&MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(paddr)&&count>=MMU_DESCRIPTOR_L2_NUMBERS_PER_L1){/*computethearchflagsforL1sectionscache,r,w,x,domainandtype*/⑶saveCounts=OsMapSection(archMmu,flags,&vaddr,&paddr,&count);}else{/*havetouseaL2mapping,weonlyallocate4KBforL1,support0~1GB*/⑷l1Entry=OsGetPte1(archMmu->virtTtb,vaddr);⑸if(OsIsPte1Invalid(l1Entry)){OsMapL1PTE(archMmu,&l1Entry,vaddr,flags);saveCounts=OsMapL2PageContinous(l1Entry,flags,&vaddr,&paddr,&count);}elseif(OsIsPte1PageTable(l1Entry)){saveCounts=OsMapL2PageContinous(l1Entry,flags,&vaddr,&paddr,&count);}else{LOS_Panic("%s%d,unimplementedtt_entry%x/n",__FUNCTION__,__LINE__,l1Entry);}}⑺mapped+=saveCounts;}returnmapped;}3.2OsMapSectionL1Section类型页表项映射函数函数OsMapSection生成L1Section类型页表entry并保存在⑴将内存区间标签(这些标签在文件kernel\base\include\los_vm_map.h中定义,标签名称一般为VM_MAP_REGION_FLAG_XXXX)转换为MMU标签(在arch\arm\arm\include\los_mmu_descriptor_v6中定义).h,标签名称一般为MMU_DESCRIPTOR_L1_TYPE_XXXX)。(2)函数OsGetPte1Ptr(archMmu->virtTtb,*vaddr)用于获取虚拟地址对应的页表项索引地址,等于页表项基地址加上高20位虚拟地址;OsTruncPte1(*paddr)|mmu标志|MMU_DESCRIPTOR_L1_TYPE_SECTION)是物理内存地址+MMU标签+页表项Section类型值的高12位。这行语句的作用是映射虚拟地址和物理地理,在页表项中维护映射关系。这行代码比较关键,我们画下图来表示,见下图。(3)虚拟地址和物理地址分别增加1MiB,映射个数减去256(1MiB有256个4KiB大小的内存页)。staticuint32osmapsection(constlosarchmmu*Archmmu,uint32flags,vaddr_t*vaddr,paddr_t*paddr_t*paddr,uint32*count){uint32mmuflags=0;ligntrdrrs|=oscvtsecflagstoattrs|=oscvtsecflagsttrs;)|mmuFlags|MMU_DESCRIPTOR_L1_TYPE_SECTION);⑶*count-=MMU_DESCRIPTOR_L2_NUMBERS_PER_L1;*vaddr+=MMU_DESCRIPTOR_L1_SMALL_SIZE;*paddr+=MMU_DESCRIPTOR_L1_SMALL_SIZE;returnMMU_DESCRIPTOR_L2_NUMBERS_PER_L1;}3.3函数OsGetL2Table生成L2页表项基地址函数OsGetL2Table用于生成L2页表,函数参数中archMmu是MMU结构,l1Index是L1页表项索引(页码),ppa是输出参数,保存L2页表项基地址。⑴计算L2页表项的偏移值(为什么要这样计算?TODO看不懂),其中(MMU_DESCRIPTOR_L2_SMALL_SIZE/MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE)的大小等于1024;l1Index&(MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE-20bit1)为虚拟地址(2)通过循环遍历检查是否存在二级页表(为什么要查询4次?TODO),以及(3)获取页表项的基地址,然后判断是否是页表的类型。如果是,则返回L2页表条目的基地址。如果没有现成的页表,则为L2页表申请内存。如果支持虚拟地址LOSCFG_KERNEL_VM,则执行(4)使用LOS_PhysPageAlloc申请内存页,将申请的内存页挂载到页表链表上,根据内存页计算虚拟内存地址kvaddr;如果不支持虚拟地址,执行(5)使用LOS_MemAlloc申请内存。⑴转换为物理地址,然后加上页表偏移值l2Offset,返回L2页表项的基地址。STATICSTATUS_TOsGetL2Table(LosArchMmu*archMmu,UINT32l1Index,paddr_t*ppa){UINT32index;PTE_TttEntry;VADDR_T*kvaddr=NULL;⑴UINT32l2Offset=(MMU_DESCRIPTOR_L2_SMALL_SIZE/MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE)*(l1Index&(MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE-1));/*lookupanexistingl2pagetable*/⑵for(index=0;indexvirtTtb[ROUNDDOWN(l1Index,MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE)+index];if((ttEntry&MMU_DESCRIPTOR_L1_TYPE_MASK)==MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE){*ppa=(PADDR_T)ROUNDDOWN(MMU_DESCRIPTOR_L1_PAGE_TABLE_ADDR(ttEntry),MMU_DESCRIPTOR_L2_SMALL_SIZE)+l2Offset;returnLOS_OK;}}#ifdefLOSCFG_KERNEL_VM/*notfound:allocateone(paddr)*/⑷LosVmPage*vmPage=LOS_PhysPageAlloc();if(vmPage==NULL){VM_ERR("havenomemorytosavel2page");returnLOS_ERRNO_VM_NO_MEMORY;}LOS_ListAdd(&archMmu->ptList,&vmPage->node);kvaddr=OsVmPageToVaddr(vmPage);#else⑸kvaddr=LOS_MemAlloc(OS_SYS_MEM_ADDR,MMU_DESCRIPTOR_L2_SMALL_SIZE);if(kvaddr==NULL){VM_ERR("havenomemorytosavel2page");returnLOS_ERRNO_VM_NO_MEMORY;}#endif(VOID)memset_s(kvaddr,M??MU_DESCRIPTOR_L2_SMALL_SIZE,0,MMU_DESCRIPTOR_L2_SMALL_SIZE);/*getphysicaladdress*/⑹*ppa=LOS_PaddrQuery(kvaddr)+l2Offset;returnLOS_OK;}3.4OsMapL1PTEL1PageTable类型页表项映射函数对应函数OsMapSection,函数OsMapL1PTE用于生成并保存L1PageTable类型页表项,其中函数参数pte1Ptr为L1页表项地址(1)调用函数OsGetL2Table()获取L2页表项的基地址TTB,(2)添加L2页表项的基地址加上描述符类型作为L1页表项数据.从⑶开始的3行代码为页表项设置了标签,⑷为虚拟内存地址vaddr存储了页表项数据。STATICVOIDOsMapL1PTE(LosArchMmu*archMmu,PTE_T*pte1Ptr,vaddr_tvaddr,UINT32flags){paddr_tpte2Base=0;⑴if(OsGetL2Table(archMmu,OsGetPte1Index(vaddr),&pte2Base)!=LOS_OK){LOS_LINEPanic("%s\%naildate"__,__FUNC__);}⑵*pte1Ptr=pte2Base|MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE;⑶if(flags&VM_MAP_REGION_FLAG_NS){*pte1Ptr|=MMU_DESCRIPTOR_L1_PAGETABLE_NON_SECURE;}*pte1Ptr&=MMU_DESCRIPTOR_L1_SMALL_DOMAIN_MASK;*pte1Ptr|=MMU_DESCRIPTOR_L1_SMALL_DOMAIN_CLIENT;//useclientAP⑷OsSavePte1(OsGetPte1Ptr(archMmu->virtTtb,vaddr),*pte1Ptr);}3.5OsMapL2PageContinuous映射二级页表函数函数OsMapL2PageContinuous用于映射二级页表项,其中函数参数pte1为一级页表项数据,flags为虚实映射标号,vaddr为虚拟内存,paddr为物理memory,count是要映射的内存页数。(1)根据L1页表项数据获取L2页表项的虚拟内存基地址。页表项的高22位是L2页表项的物理内存基地址,然后转换为虚拟内存基地址。⑵、地址区间标号转换为L2页表标号,⑶处,连续设置L2页表项数据,saveCounts表示映射了多少个L2页表项。(4)映射L2页表项数据后,更新虚拟内存地址和物理内存地址,更新映射内存页数count。由于一个L2页表项占用4KiB,saveCounts页表项需要左移12位来增加内存地址。STATICUINT32OsMapL2PageContinuous(PTE_Tpte1,UINT32flags,VADDR_T*vaddr,PADDR_T*paddr,UINT32*count){PTE_T*pte2BasePtr=NULL;UINT32archFlags;UINT32saveCounts;⑴pte2BasePtr=OsGetPteULrorsL2BasePtrp(%d,x);{#Pan(%d,x);__FUNCTION__,__LINE__,pte1);}/*computethearchflagsforL24Kpages*/⑵archFlags=OsCvtPte2FlagsToAttrs(flags);⑶saveCounts=OsSavePte2Continuous(pte2BasePtr,OsGetPte2Index(*vaddr|archlaglag),*countp;⑷*paddr+=(saveCounts<