SylixOS中ARM架构的MMU实现分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SylixOS中ARM架构的MMU实现分析相关的知识,希望对你有一定的参考价值。

1. 理论知识

1.1 快表(TLB)与页表

在虚拟页式存储管理中设置了快表(TLB),用于保存正在运行进程页表的子集,通常快表存放在高速缓冲存储器(Cache)中。
而页表存放在内存中,并通过特殊功能寄存器(TTB)等告知系统页表存储在内存中的基址。

1.2 一级页表格式

1.2.1 一级页表描述符地址转换

技术分享图片
ARM的一级页表的描述符中的地址区域可以指向一个段描述符的地址,也可以指向二级页表的地址。

1.2.2 一级页表描述符内容

SylixOS中采用了Coarse page table(粗粒度二级页表),即一级页表的描述符中的地址区域指向的是二级页表的基址。
技术分享图片

  • Bits[1:0]:表示描述符的类型,0b01表示是一个粗粒度页表描述符。
  • Bits[4:2]:未定义,为0。
  • Bits[8:5]:页表描述符所指定的页的域属性。
  • Bit[9]:未定义。
  • Bits[31:10]:指向二级页表访问的基址,需1KB对齐。

1.3 二级页表格式

1.3.1 使用粗粒度二级页表的转换过程

技术分享图片
1) 通过“转换表基址”与“虚拟地址”,得到一级页表描述符的地址;
2) 即可以得到一级描述符;
3) 将一级描述符中的“二级页表基址部分”与“虚拟地址的[19:12]位”合并,得到二级页表描述符的地址;
4) 即可以得到二级描述符。

1.3.2 二级页表描述符内容

SylixOS使用了Extended small page,即每个页以4KB为对齐。
技术分享图片

  • Bits[1:0]:表示描述符的类型,0b1Xn表示是一个扩展的小页表描述符。
  • Bits[3:2]:可缓冲标志位。
  • Bits[5:4]:访问权限位。
  • Bits[31:12]:小页的页面基地址,4KB对齐。

1.4 虚拟地址到物理地址的转换过程

技术分享图片
1) 通过“转换表基址”与“虚拟地址”,得到一级页表描述符的地址,即可得到一级描述符;
2) 将一级描述符中的“二级页表基址部分”与“虚拟地址的[19:12]位”合并,得到二级页表描述符的地址,即可得到二级描述符。
3) 将二级描述符中的“页基址”与“虚拟地址的[11:0]位”合并,得到转换后的物理地址。
#2. ARM架构实现的MMU接口

系统接口 ARM架构接口 功能描述
__VMM_MMU_MEM_INIT armMmuMemInit 初始化MMU页表内存
__VMM_MMU_GLOBAL_INIT armMmuGlobalInit 体系相关的关键数据初始化
__VMM_MMU_PGD_ALLOC armMmuPgdAlloc 分配一级页表描述符
__VMM_MMU_PGD_OFFSET armMmuPgdOffset 获取一级页表描述符地址
__VMM_MMU_PTE_ALLOC armMmuPteAlloc 分配二级页表描述符
__VMM_MMU_MAKE_TRANS armMmuMakeTrans 设置页面映射关系
__VMM_MMU_INV_TLB armMmuInvTLB 无效快表操作
__VMM_MMU_MAKE_CURCTX armMmuMakeCurCtx 设置MMU当前上下文

2.1 初始化MMU页表内存

在SylixOS中,一级页表全局公有,这样在进程切换的过程中无需切换页表,保证了实时性。
armMmuMemInit函数的功能有如下几点:
1、 开辟一级页表内存,默认的一级页表大小为16KB,页表数量为1个;
2、 开辟二级页表内存,默认的二级页表大小为1KB,页表数量为2048个;
3、 开辟的内存都采用定长分区的管理方式,根据对应页表数量进行定长分区初始化。

2.2 体系相关的关键数据初始化

armMmuGlobalInit函数内根据ARM架构的特殊性,对其定义的相关特殊功能寄存器进行了设置,如:
1、 快表置无效;
2、 设置域属性。
此函数对应不同平台时,需要设置的内容项会不同。

2.3 一级页表相关操作

2.3.1 分配一级页表描述符

static LW_PGD_TRANSENTRY *armMmuPgdAlloc (PLW_MMU_CONTEXT  pmmuctx, 
                                                                                     addr_t                          ulAddr)
{
    REGISTER LW_PGD_TRANSENTRY  *p_pgdentry = 
    (LW_PGD_TRANSENTRY *)API_PartitionGet(_G_hPGDPartition);

    if (!p_pgdentry) {
        return  (LW_NULL);
    }

    lib_bzero(p_pgdentry, PGD_BLOCK_SIZE);          /*  新的 PGD 无有效的页表项  */

    /*  获取一级页表描述符的地址  */
    p_pgdentry = (LW_PGD_TRANSENTRY *)((addr_t)p_pgdentry
               | ((ulAddr >> LW_CFG_VMM_PGD_SHIFT) << 2));  

    return  (p_pgdentry);
}

此函数在初始化的过程中调用,对其操作的分析:
1、 获取一级页表对应的定长分区基址,之后将一级页表内所有内容置空;
2、 当ulAddr为0时,返回的是一级页表的基地址;否则返回的是对应的一级页表描述符的地址,其地址转换的规则如图 1.1所示。

2.3.2 获取一级页表描述符地址

static LW_PGD_TRANSENTRY *armMmuPgdOffset (PLW_MMU_CONTEXT  pmmuctx, 
                                                                                       addr_t                          ulAddr)
{
    REGISTER LW_PGD_TRANSENTRY  *p_pgdentry = pmmuctx->MMUCTX_pgdEntry;

    p_pgdentry = (LW_PGD_TRANSENTRY *)((addr_t)p_pgdentry
        | ((ulAddr >> LW_CFG_VMM_PGD_SHIFT) << 2));                 /*  获得一级页表描述符地址 */

    return  (p_pgdentry);
}

此函数返回的是一级页表描述符的地址,其地址转换的规则如图 1.1所示。

2.3.3 构建一级页表描述符

static LW_PGD_TRANSENTRY  armMmuBuildPgdesc (UINT32  uiBaseAddr,
                                            UINT8    ucAP,
                                            UINT8    ucAP2,
                                            UINT8    ucDomain,
                                            UINT8    ucCB,
                                            UINT8    ucTEX,
                                            UINT8    ucXN,
                                            UINT8    ucType)
{
    LW_PGD_TRANSENTRY   uiDescriptor;

    switch (ucType) {

    case COARSE_TBASE:                                  /*  指向二级页表粗粒度描述符  */
        uiDescriptor = (uiBaseAddr & 0xFFFFFC00)
                       | (ucDomain <<  5)
                       | (VMSA_NS  <<  3)
                       | ucType;
        break;

       ……

    default:
        uiDescriptor = 0;                                /*  访问失效                 */
        break;
    }

    return  (uiDescriptor);
}

armMmuBuildPgdesc函数按照格式构建了一级页表描述符的内容,其为指向一个二级页表的粗粒度描述符。

2.4 二级页表相关操作

2.4.1 分配二级页表描述符

static LW_PTE_TRANSENTRY  *armMmuPteAlloc (PLW_MMU_CONTEXT        pmmuctx, 
                                                                                      LW_PMD_TRANSENTRY  *p_pmdentry,
                                                                                      addr_t                               ulAddr)
{
#if LW_CFG_CACHE_EN > 0
    INTREG              iregInterLevel;
#endif                                                  /*  LW_CFG_CACHE_EN > 0         */
    UINT8               ucAP;                               /*  存储权限                     */
    UINT8               ucDomain;                       /*  域                          */
    UINT8               ucCB;                               /*  CACHE 与缓冲区控制           */
    UINT8               ucAP2;                             /*  存储权限                     */
    UINT8               ucTEX;                             /*  CACHE 与缓冲区控制           */
    UINT8               ucXN;                               /*  永不执行位                   */

    LW_PTE_TRANSENTRY  *p_pteentry = 
    (LW_PTE_TRANSENTRY *)API_PartitionGet(_G_hPTEPartition);

    if (!p_pteentry) {
        return  (LW_NULL);
    }

    lib_bzero(p_pteentry, PTE_BLOCK_SIZE);

    armMmuFlags2Attr(LW_VMM_FLAG_VALID   |
                          LW_VMM_FLAG_ACCESS  |
                          LW_VMM_FLAG_GUARDED |
                          LW_VMM_FLAG_WRITABLE|
                          LW_VMM_FLAG_EXECABLE,
                           &ucAP, &ucAP2,
                           &ucDomain,
                           &ucCB, &ucTEX,
                           &ucXN);

  /*  设置一级页表描述符的内容,即设置二级页面基地址          */
  *p_pmdentry = (LW_PMD_TRANSENTRY)armMmuBuildPgdesc((UINT32)p_pteentry,
                                                                                                                   ucAP, ucAP2,
                                                                                                                   ucDomain,
                                                                                                                   ucCB, ucTEX,
                                                                                                                   ucXN,
                                                                                                                   COARSE_TBASE);   
#if LW_CFG_CACHE_EN > 0
    iregInterLevel = KN_INT_DISABLE();
    armDCacheFlush((PVOID)p_pmdentry, (PVOID)p_pmdentry, 32);/*第三个参数无影响*/
    KN_INT_ENABLE(iregInterLevel);
#endif                                               /*  LW_CFG_CACHE_EN > 0         */

    return  (armMmuPteOffset(p_pmdentry, ulAddr));
}

此函数操作的分析:

  • armMmuFlags2Attr函数的作用是将SylixOS使用的MMU标志按照ARM架构进行对应转换。
  • 调用armMmuBuildPgdesc构建一级页表描述符的内容。
  • 调用armMmuPteOffset获取二级页表描述符地址。

2.4.2 获取二级页表描述符地址

static  LW_PTE_TRANSENTRY *armMmuPteOffset (LW_PMD_TRANSENTRY  *p_pmdentry,
                                                                                       addr_t                               ulAddr)
{
    REGISTER LW_PTE_TRANSENTRY  *p_pteentry;
    REGISTER UINT32                                   uiTemp;

    uiTemp = (UINT32)(*p_pmdentry);                       /*  获得二级页表描述符          */

    /*  二级页表基地址为一级描述符的【31-10】位  */
    p_pteentry = (LW_PTE_TRANSENTRY *)(uiTemp & (~(LW_CFG_KB_SIZE - 1)));
                                                          /*  获得粗粒度二级页表基地址   */
    /*  二级页表描述符地址为 二级页表基址 + 虚拟地址的【19-12】位,见1.3.1  */
    p_pteentry = (LW_PTE_TRANSENTRY *)((addr_t)p_pteentry
                                                                         | ((ulAddr >> 10) & 0x3FC));               /*  获得对应虚拟地址页表描述符  */

    return  (p_pteentry);
}

2.4.3 构建二级页表描述符

static LW_PTE_TRANSENTRY  armMmuBuildPtentry (UINT32  uiBaseAddr,
                                              UINT8   ucAP,
                                              UINT8   ucAP2,
                                              UINT8   ucDomain,
                                              UINT8   ucCB,
                                              UINT8   ucTEX,
                                              UINT8   ucXN,
                                              UINT8   ucType)
{
    LW_PTE_TRANSENTRY   uiDescriptor;

    switch (ucType) {

    case SMALLPAGE_DESC:                            /*  小页描述符                  */
        uiDescriptor = (uiBaseAddr & 0xFFFFF000)
                                  | (VMSA_nG  << 11)
                                  | (VMSA_S   << 10)
                                  | (ucAP2    <<  9)
                                  | (ucTEX    <<  6)
                                  | (ucAP     <<  4)
                                  | (ucCB     <<  2)
                                  | (ucXN     <<  0)
                                  | ucType;
        break;

    default:
        uiDescriptor = 0;                             /*  访问失效                    */
        break;
    }

    return  (uiDescriptor);
}

2.5 其他MMU的操作接口

2.5.1 设置MMU当前上下文

static VOID  armMmuMakeCurCtx (PLW_MMU_CONTEXT  pmmuctx)
{
    REGISTER LW_PGD_TRANSENTRY  *p_pgdentry;

    if (LW_NCPUS > 1) {
        /*
         *  Set location of level 1 page table
         * ------------------------------------
         *  31:14   - Base addr
         *  13:7    - 0x0
         *  6       - IRGN[0]   0x1 (Normal memory, 
         *                                 Inner Write-Back Write-Allocate Cacheable)
         *  5       - NOS       0x0 (Outer Shareable)
         *  4:3     - RGN       0x1 (Normal memory, 
         *                                 Outer Write-Back Write-Allocate Cacheable)
         *  2       - IMP       0x0
         *  1       - S         0x1 (Shareable)
         *  0       - IRGN[1]   0x0 (Normal memory, 
         *                                 Inner Write-Back Write-Allocate Cacheable)
         */
        p_pgdentry = (LW_PGD_TRANSENTRY *)((ULONG)pmmuctx->MMUCTX_pgdEntry
                      | (1 << 6)
                      | ((!VMSA_S) << 5)
                      | (1 << 3)
                      | (0 << 2)
                      | (VMSA_S << 1)
                      | (0 << 0));
    } else {
        /*
         *  Set location of level 1 page table
         * ------------------------------------
         *  31:14   - Base addr
         *  13:7    - 0x0
         *  5       - NOS   0x1 (Outer NonShareable)
         *  4:3     - RGN   0x1 (Normal memory, 
         *                            Outer Write-Back Write-Allocate Cacheable)
         *  2       - IMP   0x0
         *  1       - S     0x0 (NonShareable)
         *  0       - C     0x1 (Inner cacheable)
         */
        p_pgdentry = (LW_PGD_TRANSENTRY *)((ULONG)pmmuctx->MMUCTX_pgdEntry
                      | ((!VMSA_S) << 5)
                      | (1 << 3)
                      | (0 << 2)
                      | (VMSA_S << 1)
                      | (1 << 0));
    }

    armMmuSetTTBase(p_pgdentry);
    armMmuSetTTBase1(p_pgdentry);
}

TTBR0、TTBR1的格式如下图所示。

TTBR0的格式:
技术分享图片
TTBR1的格式:
技术分享图片

以上是关于SylixOS中ARM架构的MMU实现分析的主要内容,如果未能解决你的问题,请参考以下文章

SylixOS 中断流程

"arch/arm/kernel/head.S"里面一点片段的理解

arm-linux内核start_kernel之前启动分析-开启MMU,走进新时代

鸿蒙轻内核源码分析:MMU协处理器

SylixOS 调试方法详解——静态代码分析

手机ARM种类,STM32中的ARM核又是什么东东?