更多内容请访问: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;index
