文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

鸿蒙轻内核A核源码分析系列之虚实映射(3)虚拟物理内存映射

2024-12-02 15:18

关注

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

3、虚实映射函数LOS_ArchMmuMap

从上文可知,用户程序加载启动时,会将代码段、数据段映射进虚拟内存空间,此时并没有物理页做实际的映射;程序执行时,如下图(图片来自OpenHarmony docs开源站点)粗箭头所示,CPU访问虚拟地址,通过MMU查找是否有对应的物理内存,若该虚拟地址无对应的物理地址则触发缺页异常,内核申请物理内存并将虚实映射关系及对应的属性配置信息写进页表,并把页表条目缓存至TLB,接着CPU可直接通过转换关系访问实际的物理内存;若CPU访问已缓存至TLB的页表条目,无需再访问保存在内存中的页表,可加快查找速度。本小节我们就详细分析下虚实映射函数的实现代码。


3.1 函数LOS_ArchMmuMap

函数LOS_ArchMmuMap用于映射进程空间虚拟地址区间与物理地址区间,其中输入参数archMmu为MMU结构体,vaddr和paddr分别是虚拟内存和物理内存的开始地址;count为虚拟地址和物理地址映射的内存页数量;flags为映射标签。⑴处进行函数参数校验,不支持NON-SECURE的标记,虚拟地址和物理地址需要内存页4KiB对齐,参数检查函数代码简单,自行查看即可。⑵处当虚拟地址、物理地址基于1MiB对齐,并且数量count大于256时,使用Section页表项格式。⑶处生成L1 section类型页表项并保存,下文详细分析该函数OsMapSection()。如果不满足⑵处条件,需要使用L2映射。首先执行⑷处获取虚拟地址vaddr对应的L1页表项,接着执行⑸处判断是否映射,如果没有对应的映射,则执行⑹处的函数OsMapL1PTE生成L1 page table类型页表项并保存,然后执行函数OsMapL2PageContinous生成L2 页表项并保存。如果已经映射为L1 page table页表项类型,则执行函数OsMapL2PageContinous生成L2 页表项并保存。如果不是支持的页表项类型,则执行LOS_Panic()触发异常。⑺处统计生成映射的调试,最终会返回映射成功的数量。

可以看出,在给定虚实内存地址和映射的内存页数后,使用L1页表映射还是L2页表映射的判断条件是:虚实内存地址是否1MiB内存对齐,并且映射数量是否大于256。当使用L1映射时,映射为Section页表项类型。当使用L2映射时,根据L1页表项类型,分布处理无效页表项和Page Table页表项类型这2种情况。具体的映射方式见下文。

  1. status_t LOS_ArchMmuMap(LosArchMmu *archMmu, VADDR_T vaddr, PADDR_T paddr, size_t count, UINT32 flags) 
  2.     PTE_T l1Entry; 
  3.     UINT32 saveCounts = 0; 
  4.     INT32 mapped = 0; 
  5.     INT32 checkRst; 
  6.  
  7. ⑴  checkRst = OsMapParamCheck(flags, vaddr, paddr); 
  8.     if (checkRst < 0) { 
  9.         return checkRst; 
  10.     } 
  11.  
  12.      
  13.     while (count > 0) { 
  14. ⑵      if (MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(vaddr) && 
  15.             MMU_DESCRIPTOR_IS_L1_SIZE_ALIGNED(paddr) && 
  16.             count >= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1) { 
  17.              
  18. ⑶          saveCounts = OsMapSection(archMmu, flags, &vaddr, &paddr, &count); 
  19.         } else { 
  20.              
  21. ⑷          l1Entry = OsGetPte1(archMmu->virtTtb, vaddr); 
  22. ⑸          if (OsIsPte1Invalid(l1Entry)) { 
  23. ⑹              OsMapL1PTE(archMmu, &l1Entry, vaddr, flags); 
  24.                 saveCounts = OsMapL2PageContinous(l1Entry, flags, &vaddr, &paddr, &count); 
  25.             } else if (OsIsPte1PageTable(l1Entry)) { 
  26.                 saveCounts = OsMapL2PageContinous(l1Entry, flags, &vaddr, &paddr, &count); 
  27.             } else { 
  28.                 LOS_Panic("%s %d, unimplemented tt_entry %x/n", __FUNCTION__, __LINE__, l1Entry); 
  29.             } 
  30.         } 
  31. ⑺      mapped += saveCounts; 
  32.     } 
  33.  
  34.     return mapped; 

3.2 OsMapSectionL1 Section类型页表项映射函数

函数OsMapSection生成L1 Section类型页表项并保存。⑴处把内存区间标签(这些标签定义在文件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)。 ⑵处的函数OsGetPte1Ptr(archMmu->virtTtb, *vaddr)用于获取虚拟地址对应的页表项索引地址,等于页表项基地址加上虚拟地址的高20位;OsTruncPte1(*paddr) | mmuFlags | MMU_DESCRIPTOR_L1_TYPE_SECTION)为物理内存地址的高12位+MMU标签+页表项Section类型值。该行语句的作用是把虚拟地址和物理地理进行映射,映射关系维护在页表项。这行代码比较关键,我们绘制下图形来表示,见下图。⑶处把虚拟地址和物理地址增加1MiB的大小,映射数量减去256(1MiB有256个4KiB大小的内存页)。

 

  1. STATIC UINT32 OsMapSection(const LosArchMmu *archMmu, UINT32 flags, VADDR_T *vaddr, 
  2.                            PADDR_T *paddr, UINT32 *count
  3.     UINT32 mmuFlags = 0; 
  4.  
  5. ⑴  mmuFlags |= OsCvtSecFlagsToAttrs(flags); 
  6. ⑵  OsSavePte1(OsGetPte1Ptr(archMmu->virtTtb, *vaddr), 
  7.         OsTruncPte1(*paddr) | mmuFlags | MMU_DESCRIPTOR_L1_TYPE_SECTION); 
  8. ⑶  *count -= MMU_DESCRIPTOR_L2_NUMBERS_PER_L1; 
  9.     *vaddr += MMU_DESCRIPTOR_L1_SMALL_SIZE; 
  10.     *paddr += MMU_DESCRIPTOR_L1_SMALL_SIZE; 
  11.  
  12.     return MMU_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 - 1)为虚拟地址的第20-21位。⑵处通过循环遍历查询是否存在L2页表(为啥查询4次?TODO),⑶处获取页表项基地址,然后判断是否页表类型,如果是,则返回L2页表项基地址。

如果没有存在的页表,则为L2页表申请内存,如果支持虚拟地址LOSCFG_KERNEL_VM,执行⑷使用LOS_PhysPageAlloc申请内存页,把申请的内存页挂载页表链表上,并根据内存页计算虚拟内存地址kvaddr;如果不支持虚拟地址,执行⑸使用LOS_MemAlloc申请内存。⑹处转换为物理地址,然后加上页表偏移值l2Offset返回L2页表项基地址。

  1. STATIC STATUS_T OsGetL2Table(LosArchMmu *archMmu, UINT32 l1Index, paddr_t *ppa) 
  2.     UINT32 index
  3.     PTE_T ttEntry; 
  4.     VADDR_T *kvaddr = NULL
  5. ⑴  UINT32 l2Offset = (MMU_DESCRIPTOR_L2_SMALL_SIZE / MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE) * 
  6.         (l1Index & (MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE - 1)); 
  7.      
  8. ⑵   for (index = 0; index < MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE; index++) { 
  9. ⑶      ttEntry = archMmu->virtTtb[ROUNDDOWN(l1Index, MMU_DESCRIPTOR_L1_SMALL_L2_TABLES_PER_PAGE) + index]; 
  10.         if ((ttEntry & MMU_DESCRIPTOR_L1_TYPE_MASK) == MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE) { 
  11.             *ppa = (PADDR_T)ROUNDDOWN(MMU_DESCRIPTOR_L1_PAGE_TABLE_ADDR(ttEntry), MMU_DESCRIPTOR_L2_SMALL_SIZE) + 
  12.                 l2Offset; 
  13.             return LOS_OK; 
  14.         } 
  15.     } 
  16.  
  17. #ifdef LOSCFG_KERNEL_VM 
  18.      
  19. ⑷  LosVmPage *vmPage = LOS_PhysPageAlloc(); 
  20.     if (vmPage == NULL) { 
  21.         VM_ERR("have no memory to save l2 page"); 
  22.         return LOS_ERRNO_VM_NO_MEMORY; 
  23.     } 
  24.     LOS_ListAdd(&archMmu->ptList, &vmPage->node); 
  25.     kvaddr = OsVmPageToVaddr(vmPage); 
  26. #else 
  27. ⑸  kvaddr = LOS_MemAlloc(OS_SYS_MEM_ADDR, MMU_DESCRIPTOR_L2_SMALL_SIZE); 
  28.     if (kvaddr == NULL) { 
  29.         VM_ERR("have no memory to save l2 page"); 
  30.         return LOS_ERRNO_VM_NO_MEMORY; 
  31.     } 
  32. #endif 
  33.     (VOID)memset_s(kvaddr, MMU_DESCRIPTOR_L2_SMALL_SIZE, 0, MMU_DESCRIPTOR_L2_SMALL_SIZE); 
  34.  
  35.      
  36. ⑹  *ppa = LOS_PaddrQuery(kvaddr) + l2Offset; 
  37.     return LOS_OK; 

3.4 OsMapL1PTEL1 Page Table类型页表项映射函数

和函数OsMapSection对应,函数OsMapL1PTE用于生成L1 Page Table类型页表项并保存,其中函数参数pte1Ptr是L1页表项地址。⑴处调用函数OsGetL2Table()获取L2页表项基地址TTB,⑵处把L2页表项基地址加上描述符类型作为L1页表项数据。⑶处开始的3行代码为页表项设置标签,⑷处为虚拟内存地址vaddr保存页表项数据。

  1. STATIC VOID OsMapL1PTE(LosArchMmu *archMmu, PTE_T *pte1Ptr, vaddr_t vaddr, UINT32 flags) 
  2.     paddr_t pte2Base = 0; 
  3.  
  4. ⑴  if (OsGetL2Table(archMmu, OsGetPte1Index(vaddr), &pte2Base) != LOS_OK) { 
  5.         LOS_Panic("%s %d, failed to allocate pagetable\n", __FUNCTION__, __LINE__); 
  6.     } 
  7.  
  8. ⑵  *pte1Ptr = pte2Base | MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE; 
  9. ⑶  if (flags & VM_MAP_REGION_FLAG_NS) { 
  10.         *pte1Ptr |= MMU_DESCRIPTOR_L1_PAGETABLE_NON_SECURE; 
  11.     } 
  12.     *pte1Ptr &= MMU_DESCRIPTOR_L1_SMALL_DOMAIN_MASK; 
  13.     *pte1Ptr |= MMU_DESCRIPTOR_L1_SMALL_DOMAIN_CLIENT; // use client AP 
  14. ⑷   OsSavePte1(OsGetPte1Ptr(archMmu->virtTtb, vaddr), *pte1Ptr); 

3.5 OsMapL2PageContinuous映射L2页表函数

函数OsMapL2PageContinuous用于映射L2页表项,其中函数参数pte1为L1页表项数据,flags为虚实映射标签,vaddr为虚拟内存,paddr为物理内存,count为需要映射的内存页数量。

⑴处根据L1页表项数据获取L2页表项虚拟内存基地址。页表项的高22位为L2页表项的物理内存基地址,然后转换为虚拟内存基地址即可。⑵处把地址区间标签转换为L2页表标签,⑶处连续设置L2页表项数据,saveCounts表示映射了多少个L2页表项。⑷处映射L2页表项数据后,更新虚拟、物理内存地址,更新映射后的内存页数量count。由于一个L2页表项占用4KiB大小,saveCounts个页表项,需要把saveCounts左移12位来增长内存地址。

  1. STATIC UINT32 OsMapL2PageContinuous(PTE_T pte1, UINT32 flags, VADDR_T *vaddr, PADDR_T *paddr, UINT32 *count
  2.     PTE_T *pte2BasePtr = NULL
  3.     UINT32 archFlags; 
  4.     UINT32 saveCounts; 
  5.  
  6. ⑴  pte2BasePtr = OsGetPte2BasePtr(pte1); 
  7.     if (pte2BasePtr == NULL) { 
  8.         LOS_Panic("%s %d, pte1 %#x error\n", __FUNCTION__, __LINE__, pte1); 
  9.     } 
  10.  
  11.      
  12. ⑵  archFlags = OsCvtPte2FlagsToAttrs(flags); 
  13. ⑶  saveCounts = OsSavePte2Continuous(pte2BasePtr, OsGetPte2Index(*vaddr), *paddr | archFlags, *count); 
  14. ⑷  *paddr += (saveCounts << MMU_DESCRIPTOR_L2_SMALL_SHIFT); 
  15.     *vaddr += (saveCounts << MMU_DESCRIPTOR_L2_SMALL_SHIFT); 
  16.     *count -= saveCounts; 
  17.     return saveCounts; 

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

来源:鸿蒙社区内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯