鸿蒙轻内核A核源码分析系列之虚实映射(1)基础概念

开发 前端
虚实映射其实就是一个建立页表的过程。MMU支持多级页表,LiteOS-A内核采用二级页表描述进程空间。首先介绍下一级页表和二级页表。

[[437938]]

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

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

https://harmonyos.51cto.com

虚实映射是指系统通过内存管理单元(Memory Management Unit,MMU)将进程空间的虚拟地址(VA)与实际的物理地址(PA)做映射,并指定相应的访问权限、缓存属性等。程序执行时,CPU访问的是虚拟内存,通过MMU找到映射的物理内存,并做相应的代码执行或数据读写操作。MMU的映射由页表(Page Table)来描述,页表保存虚拟地址和物理地址的映射关系以及访问权限等。每个进程在创建的时候都会创建一个页表,页表由一个个页表条目(Page Table Entry, PTE)构成,每个页表条目描述虚拟地址区间与物理地址区间的映射关系。页表数据在内存区域存储位置的开始地址叫做转换表基地址/页表基地址(Translation Table Base,TTB)。MMU中有一块页表缓存,称为快表(Translation Lookaside Buffers, TLB),它缓存最近查找过的VA对应的页表项。做地址转换时,MMU首先在TLB中查找,如果找到对应的页表项,则可直接进行转换,否则就要去物理内存中读取页表项。TLB缓存可以减少访问物理内存的次数,提升查询效率。

本文中所涉及的源码,以OpenHarmony LiteOS-A内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_a 获取。如果涉及开发板,则默认以hispark_taurus为例。MMU相关的操作函数主要在文件arch/arm/arm/src/los_arch_mmu.c中定义。

虚实映射其实就是一个建立页表的过程。MMU支持多级页表,LiteOS-A内核采用二级页表描述进程空间。首先介绍下一级页表和二级页表。

1、一级页表L1和二级页表L2

1.1 页表项基础概念

L1页表将全部的4GiB虚拟内存地址空间划分为4096份,每份大小1MiB。每份对应一个32位的页表项,内容是L2页表基地址TTB或某个1MiB大小的物理内存的地址。其中高12位记录页号,用于对页表项定位,也就是4096个页表项的索引;低20位记录页内偏移值,虚实地址页内偏移值相等。使用虚拟地址中的虚拟页号查询页表得到对应的物理页号,然后与虚拟地址中的页内位移组成物理地址。每个L1页表项将1MiB的虚拟内存地址转换为物理地址。如下图所示:

鸿蒙轻内核A核源码分析系列五 虚实映射(1)基础概念-鸿蒙HarmonyOS技术社区

对于用户进程,每个一级页表条目描述符占用4个字节(即32位的L1页表项),可表示1MiB的内存空间的映射关系,即1GiB用户空间(LiteOS-A内核中用户空间占用1GiB)的虚拟内存空间需要1024个L1页表项。系统创建用户进程时,在内存中申请一块4KiB大小(=4byte*1024)的内存块作为一级页表项的存储区域,系统根据当前进程的需要会动态申请内存作为二级页表的存储区域。现在我们就知道,在虚拟内存章节,用户进程虚拟地址空间初始化函数OsCreateUserVmSpace()申请了4KiB的内存作为页表存储区域的依据了:VADDR_T *ttb = LOS_PhysPagesAllocContiguous(1);,这段内存的开始地址就是TTB页表基地址。每个用户进程需要申请自己的页表项存储区域,对于内核进程,页表项存储区域是固定的,即UINT8 g_firstPageTable[0x4000],大小为16KiB。

L1页表项的低2位用于定义页表项的类型,页表项类型有如下3种:

  • Invalid 无效页表项,虚拟地址没有映射到物理地址,访问会产生缺页异常;
  • Page Table 指向L2页表的页表项;
  • Section Section 页表项对应1MiB大小的内存块,直接使用页表项的最高12位替代虚拟地址的高12位即可得到物理地址。

L2页表把1MiB的地址范围按4KiB的内存页大小继续分成256个小页。内存的高20位记录页号,用于对页表项定位;低12位记录页内偏移值,虚实地址页内偏移值相等。使用虚拟地址中的虚拟页号查询页表得到对应的物理页号,然后与虚拟地址中的页内位移组成物理地址。每个L2页表项将4KiB的虚拟内存地址转换为物理地址。如下图所示:

鸿蒙轻内核A核源码分析系列五 虚实映射(1)基础概念-鸿蒙HarmonyOS技术社区

L2页表项的低2位用于识别页表项的类型,类型有如下4种:

Invalid 无效页表项,虚拟地址没有映射到物理地址,访问会产生缺页异常;

Large Page 大页表项,支持64KiB大页,暂不支持;

Small Page 小页表项,支持4KiB小页的二级页表映射;

Small Page XN 小页表项扩展。

在文件arch/arm/arm/include/los_mmu_descriptor_v6.h中定义了页表项类型,代码如下:

  1. /* L1 descriptor type */ 
  2. #define MMU_DESCRIPTOR_L1_TYPE_INVALID                          (0x0 << 0) 
  3. #define MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE                       (0x1 << 0) 
  4. #define MMU_DESCRIPTOR_L1_TYPE_SECTION                          (0x2 << 0) 
  5. #define MMU_DESCRIPTOR_L1_TYPE_MASK                             (0x3 << 0) 
  6.  
  7. /* L2 descriptor type */ 
  8. #define MMU_DESCRIPTOR_L2_TYPE_INVALID                          (0x0 << 0) 
  9. #define MMU_DESCRIPTOR_L2_TYPE_LARGE_PAGE                       (0x1 << 0) 
  10. #define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE                       (0x2 << 0) 
  11. #define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE_XN                    (0x3 << 0) 
  12. #define MMU_DESCRIPTOR_L2_TYPE_MASK                             (0x3 << 0)     

1.2 页表项操作

在文件arch/arm/arm/include/los_pte_ops.h定义了页表项相关的操作。

1.2.1 函数OsGetPte1获取虚拟地址的L1页表项

⑴处的OsGetPte1Index()内联函数获取虚拟地址的高12位作为页表号。

⑵处的OsGetPte1Ptr()内联函数根据页表项基地址和虚拟地址获取对应的L1页表项地址。

⑶处的函数OsGetPte1()用于获取指定虚拟地址对应的L1页表项数据。该L1页表项地址由页表项基地址pte1BasePtr加上虚拟地址va对应的页表项索引(页表号)组成,其中页表项索引等于虚拟地址的高12位。

需要注意函数OsGetPte1Index()和OsGetPte1()的区别,前者是页表项内存地址,后者是页表项地址上保持的页表项数据。

  1.    STATIC INLINE UINT32 OsGetPte1Index(vaddr_t va) 
  2.     { 
  3. ⑴      return va >> MMU_DESCRIPTOR_L1_SMALL_SHIFT; 
  4.     } 
  5.  
  6.     STATIC INLINE PTE_T *OsGetPte1Ptr(PTE_T *pte1BasePtr, vaddr_t va) 
  7.     { 
  8. ⑵      return (pte1BasePtr + OsGetPte1Index(va)); 
  9.     } 
  10.  
  11.     STATIC INLINE PTE_T OsGetPte1(PTE_T *pte1BasePtr, vaddr_t va) 
  12.     { 
  13. ⑶      return *OsGetPte1Ptr(pte1BasePtr, va); 
  14.     } 

1.2.2 函数OsGetPte2获取虚拟地址的L2页表项

⑴处OsGetPte2Index()函数根据虚拟地址获取对应页表项的页表号,计算方式是把虚拟地址对1MiB取余,然后取高20位。因为L2页表项细分的是1MiB内存块,这里把虚拟地址对1MiB取余。

⑵处的函数OsGetPte2()用于获取指定虚拟地址对应的L2页表项地址。L2页表项地址由页表项基地pte2BasePtr址加上页表项索引组成,其中页表项索引等于虚拟地址对1MiB取余后的高20位。

  1.  STATIC INLINE UINT32 OsGetPte2Index(vaddr_t va) 
  2.     { 
  3. ⑴      return (va % MMU_DESCRIPTOR_L1_SMALL_SIZE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT; 
  4.     } 
  5.  
  6.     STATIC INLINE PTE_T OsGetPte2(PTE_T *pte2BasePtr, vaddr_t va) 
  7.     { 
  8. ⑵      return *(pte2BasePtr + OsGetPte2Index(va)); 
  9.     } 

1.2.3 页表项类型判断函数

从上文已经可知,每一个L1页表项的低2位标记页表项的类型,OsIsPte1PageTable、OsIsPte1Invalid、OsIsPte1Section等函数分别判断L1页表项是否是页表、无效、Section段类型。

  1. STATIC INLINE BOOL OsIsPte1PageTable(PTE_T pte1) 
  2.  { 
  3.      return (pte1 & MMU_DESCRIPTOR_L1_TYPE_MASK) == MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE; 
  4.  } 
  5.  
  6.  STATIC INLINE BOOL OsIsPte1Invalid(PTE_T pte1) 
  7.  { 
  8.      return (pte1 & MMU_DESCRIPTOR_L1_TYPE_MASK) == MMU_DESCRIPTOR_L1_TYPE_INVALID; 
  9.  } 
  10.  
  11.  STATIC INLINE BOOL OsIsPte1Section(PTE_T pte1) 
  12.  { 
  13.      return (pte1 & MMU_DESCRIPTOR_L1_TYPE_MASK) == MMU_DESCRIPTOR_L1_TYPE_SECTION; 
  14.  } 

同样,下面4个函数用于判断L2页表项的类型。

  1. STATIC INLINE BOOL OsIsPte2SmallPage(PTE_T pte2) 
  2.     return (pte2 & MMU_DESCRIPTOR_L2_TYPE_MASK) == MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE; 
  3.  
  4. STATIC INLINE BOOL OsIsPte2SmallPageXN(PTE_T pte2) 
  5.     return (pte2 & MMU_DESCRIPTOR_L2_TYPE_MASK) == MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE_XN; 
  6.  
  7. STATIC INLINE BOOL OsIsPte2LargePage(PTE_T pte2) 
  8.     return (pte2 & MMU_DESCRIPTOR_L2_TYPE_MASK) == MMU_DESCRIPTOR_L2_TYPE_LARGE_PAGE; 
  9.  
  10. STATIC INLINE BOOL OsIsPte2Invalid(PTE_T pte2) 
  11.     return (pte2 & MMU_DESCRIPTOR_L2_TYPE_MASK) == MMU_DESCRIPTOR_L2_TYPE_INVALID; 

1.2.4 OsTruncPte1函数截取物理地址的高12位

下面代码的宏定义在文件arch\arm\arm\include\los_mmu_descriptor_v6.h中定义。其中MMU_DESCRIPTOR_L1_SMALL_FRAME等于~(0x100000-1)=0xFFF00000即取高12位。所以函数OsTruncPte1截取物理内存地址的高12位。

  1. #define MMU_DESCRIPTOR_L1_SMALL_SIZE                            0x100000 
  2.    #define MMU_DESCRIPTOR_L1_SMALL_MASK                            (MMU_DESCRIPTOR_L1_SMALL_SIZE - 1) 
  3.    #define MMU_DESCRIPTOR_L1_SMALL_FRAME                           (~MMU_DESCRIPTOR_L1_SMALL_MASK) 
  4.    #define MMU_DESCRIPTOR_L1_SMALL_SHIFT                           20 
  5.    #define MMU_DESCRIPTOR_L1_SECTION_ADDR(x)                       ((x) & MMU_DESCRIPTOR_L1_SMALL_FRAME) 
  6.    ...... 
  7.    STATIC INLINE ADDR_T OsTruncPte1(ADDR_T addr) 
  8.    { 
  9.        return MMU_DESCRIPTOR_L1_SECTION_ADDR(addr); 
  10.    } 

1.2.5 L2页表项连续操作函数

⑴处的函数OsSavePte2设置L2页表项数据,页表项指针地址pte2Ptr指向的内存保存的数据写入页表项数据pte2。OsSavePte2Continuous函数用于连续设置L2页表项数据,需要的参数分别有pte2BasePtr页表基地址,index虚拟地址对应的页号作为开始索引,页表项地址pte2和连续的页表项数量count。⑵处设置页表项基地址,然后页表号增加1,页表项数量减1。⑶处更新页表项地址,增加的大小为MMU_DESCRIPTOR_L2_SMALL_SIZE,即4KiB大小,然后统计保存成功的数量加1。⑷处的while循环的条件中的MMU_DESCRIPTOR_L2_NUMBERS_PER_L1等于256(即每1MiB对应的L2页表项的数量)。

函数OsClearPte2Continuous用于清理页表项基地址。

  1. STATIC INLINE VOID OsSavePte2(PTE_T *pte2Ptr, PTE_T pte2) 
  2.     DMB; 
  3. ⑴  *pte2Ptr = pte2; 
  4.     DSB; 
  5.  
  6. STATIC INLINE UINT32 OsSavePte2Continuous(PTE_T *pte2BasePtr, UINT32 index, PTE_T pte2, UINT32 count
  7.     UINT32 saveCounts = 0; 
  8.     if (count == 0) { 
  9.         return 0; 
  10.     } 
  11.  
  12.     DMB; 
  13.     do { 
  14. ⑵      pte2BasePtr[index++] = pte2; 
  15.         count--; 
  16. ⑶      pte2 += MMU_DESCRIPTOR_L2_SMALL_SIZE; 
  17.         saveCounts++; 
  18. ⑷  } while ((count != 0) && (index != MMU_DESCRIPTOR_L2_NUMBERS_PER_L1)); 
  19.     DSB; 
  20.  
  21.     return saveCounts; 
  22.  
  23. STATIC INLINE VOID OsClearPte2Continuous(PTE_T *pte2Ptr, UINT32 count
  24.     UINT32 index = 0; 
  25.  
  26.     DMB; 
  27.     while (count > 0) { 
  28.         pte2Ptr[index++] = 0; 
  29.         count--; 
  30.     } 
  31.     DSB; 

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

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

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2021-12-03 16:22:05

鸿蒙HarmonyOS应用

2021-12-02 15:08:23

鸿蒙HarmonyOS应用

2021-12-03 16:20:26

鸿蒙HarmonyOS应用

2022-03-11 20:23:14

鸿蒙源码分析进程管理

2022-01-12 10:50:23

鸿蒙HarmonyOS应用

2022-01-10 15:31:44

鸿蒙HarmonyOS应用

2022-04-13 11:02:12

鸿蒙事件模块事件Event

2022-03-03 18:28:28

Harmony进程任务管理模块

2021-06-04 09:57:49

鸿蒙HarmonyOS应用

2022-04-13 11:12:43

鸿蒙轻内核信号量模块操作系统

2021-05-25 09:28:34

鸿蒙HarmonyOS应用

2021-10-20 16:08:57

鸿蒙HarmonyOS应用

2021-11-05 15:00:33

鸿蒙HarmonyOS应用

2021-05-17 09:28:59

鸿蒙HarmonyOS应用

2021-11-08 15:06:15

鸿蒙HarmonyOS应用

2021-06-04 14:15:10

鸿蒙HarmonyOS应用

2021-05-08 15:14:50

鸿蒙HarmonyOS应用

2022-01-14 08:39:47

鸿蒙HarmonyOS应用

2021-05-31 20:30:55

鸿蒙HarmonyOS应用

2021-06-17 09:36:07

鸿蒙HarmonyOS应用
点赞
收藏

51CTO技术栈公众号