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

虚实映射是指系统通过内存管理单元(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内核为例,均可以在开源站点 gitee.com/openharmony... 获取。如果涉及开发板,则默认以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的虚拟内存地址转换为物理地址。如下图所示:

对于用户进程,每个一级页表条目描述符占用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的虚拟内存地址转换为物理地址。如下图所示:

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

  • Invalid 无效页表项,虚拟地址没有映射到物理地址,访问会产生缺页异常;
  • Large Page 大页表项,支持64KiB大页,暂不支持;
  • Small Page 小页表项,支持4KiB小页的二级页表映射;
  • Small Page XN 小页表项扩展。

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

shell 复制代码
/* L1 descriptor type */
#define MMU_DESCRIPTOR_L1_TYPE_INVALID                          (0x0 << 0)
#define MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE                       (0x1 << 0)
#define MMU_DESCRIPTOR_L1_TYPE_SECTION                          (0x2 << 0)
#define MMU_DESCRIPTOR_L1_TYPE_MASK                             (0x3 << 0)

/* L2 descriptor type */
#define MMU_DESCRIPTOR_L2_TYPE_INVALID                          (0x0 << 0)
#define MMU_DESCRIPTOR_L2_TYPE_LARGE_PAGE                       (0x1 << 0)
#define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE                       (0x2 << 0)
#define MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE_XN                    (0x3 << 0)
#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()的区别,前者是页表项内存地址,后者是页表项地址上保持的页表项数据。

scss 复制代码
    STATIC INLINE UINT32 OsGetPte1Index(vaddr_t va)
    {
⑴      return va >> MMU_DESCRIPTOR_L1_SMALL_SHIFT;
    }

    STATIC INLINE PTE_T *OsGetPte1Ptr(PTE_T *pte1BasePtr, vaddr_t va)
    {
⑵      return (pte1BasePtr + OsGetPte1Index(va));
    }

    STATIC INLINE PTE_T OsGetPte1(PTE_T *pte1BasePtr, vaddr_t va)
    {
⑶      return *OsGetPte1Ptr(pte1BasePtr, va);
    }
    
DD一下: 欢迎大家关注公众号<程序猿百晓生>,可以了解到一下知识点。
erlang 复制代码
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案) 
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......

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

⑴处OsGetPte2Index()函数根据虚拟地址获取对应页表项的页表号,计算方式是把虚拟地址对1MiB取余,保留低20位,然后右移12位。因为L2页表项细分的是1MiB内存块,这里把虚拟地址对1MiB取余。即L2页表项中,虚拟地址对应的页表项的索引的等于虚拟内存地址的[13,20]位。

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

scss 复制代码
    STATIC INLINE UINT32 OsGetPte2Index(vaddr_t va)
    {
⑴      return (va % MMU_DESCRIPTOR_L1_SMALL_SIZE) >> MMU_DESCRIPTOR_L2_SMALL_SHIFT;
    }

    STATIC INLINE PTE_T OsGetPte2(PTE_T *pte2BasePtr, vaddr_t va)
    {
⑵      return *(pte2BasePtr + OsGetPte2Index(va));
    }

1.2.3 页表项类型判断函数

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

scss 复制代码
    STATIC INLINE BOOL OsIsPte1PageTable(PTE_T pte1)
    {
        return (pte1 & MMU_DESCRIPTOR_L1_TYPE_MASK) == MMU_DESCRIPTOR_L1_TYPE_PAGE_TABLE;
    }

    STATIC INLINE BOOL OsIsPte1Invalid(PTE_T pte1)
    {
        return (pte1 & MMU_DESCRIPTOR_L1_TYPE_MASK) == MMU_DESCRIPTOR_L1_TYPE_INVALID;
    }

    STATIC INLINE BOOL OsIsPte1Section(PTE_T pte1)
    {
        return (pte1 & MMU_DESCRIPTOR_L1_TYPE_MASK) == MMU_DESCRIPTOR_L1_TYPE_SECTION;
    }

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

scss 复制代码
STATIC INLINE BOOL OsIsPte2SmallPage(PTE_T pte2)
{
    return (pte2 & MMU_DESCRIPTOR_L2_TYPE_MASK) == MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE;
}

STATIC INLINE BOOL OsIsPte2SmallPageXN(PTE_T pte2)
{
    return (pte2 & MMU_DESCRIPTOR_L2_TYPE_MASK) == MMU_DESCRIPTOR_L2_TYPE_SMALL_PAGE_XN;
}

STATIC INLINE BOOL OsIsPte2LargePage(PTE_T pte2)
{
    return (pte2 & MMU_DESCRIPTOR_L2_TYPE_MASK) == MMU_DESCRIPTOR_L2_TYPE_LARGE_PAGE;
}

STATIC INLINE BOOL OsIsPte2Invalid(PTE_T pte2)
{
    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位。

arduino 复制代码
    #define MMU_DESCRIPTOR_L1_SMALL_SIZE                            0x100000
    #define MMU_DESCRIPTOR_L1_SMALL_MASK                            (MMU_DESCRIPTOR_L1_SMALL_SIZE - 1)
    #define MMU_DESCRIPTOR_L1_SMALL_FRAME                           (~MMU_DESCRIPTOR_L1_SMALL_MASK)
    #define MMU_DESCRIPTOR_L1_SMALL_SHIFT                           20
    #define MMU_DESCRIPTOR_L1_SECTION_ADDR(x)                       ((x) & MMU_DESCRIPTOR_L1_SMALL_FRAME)
    ......
    STATIC INLINE ADDR_T OsTruncPte1(ADDR_T addr)
    {
        return MMU_DESCRIPTOR_L1_SECTION_ADDR(addr);
    }

1.2.5 L2页表项连续操作函数

⑴处的函数OsSavePte2设置L2页表项数据,页表项指针地址pte2Ptr指向的内存保存的数据写入页表项数据pte2OsSavePte2Continuous函数用于连续设置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用于清理页表项基地址。

ini 复制代码
STATIC INLINE VOID OsSavePte2(PTE_T *pte2Ptr, PTE_T pte2)
{
    DMB;
⑴  *pte2Ptr = pte2;
    DSB;
}

STATIC INLINE UINT32 OsSavePte2Continuous(PTE_T *pte2BasePtr, UINT32 index, PTE_T pte2, UINT32 count)
{
    UINT32 saveCounts = 0;
    if (count == 0) {
        return 0;
    }

    DMB;
    do {
⑵      pte2BasePtr[index++] = pte2;
        count--;
⑶      pte2 += MMU_DESCRIPTOR_L2_SMALL_SIZE;
        saveCounts++;
⑷  } while ((count != 0) && (index != MMU_DESCRIPTOR_L2_NUMBERS_PER_L1));
    DSB;

    return saveCounts;
}

STATIC INLINE VOID OsClearPte2Continuous(PTE_T *pte2Ptr, UINT32 count)
{
    UINT32 index = 0;

    DMB;
    while (count > 0) {
        pte2Ptr[index++] = 0;
        count--;
    }
    DSB;
}
相关推荐
Van_captain12 小时前
rn_for_openharmony常用组件_Breadcrumb面包屑
javascript·开源·harmonyos
御承扬12 小时前
鸿蒙原生系列之动画效果(帧动画)
c++·harmonyos·动画效果·ndk ui·鸿蒙原生
行者9613 小时前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨13 小时前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨14 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨14 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨14 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者9615 小时前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
小雨下雨的雨16 小时前
Flutter 框架跨平台鸿蒙开发 —— Padding 控件之空间呼吸艺术
flutter·ui·华为·harmonyos·鸿蒙系统
行者9616 小时前
Flutter到OpenHarmony:横竖屏自适应布局深度实践
flutter·harmonyos·鸿蒙