ARM体系架构——MMU【转】

转自:https://www.jianshu.com/p/ef1e93e9d65b

一、前言

嵌入式Linux 开发中,往往会听到 MMU 这个词,但大多数情况下并不会去了解它,因为操作系统已经做好了关于 MMU 的一切操作,我们只需要在操作系统的框架下直接使用即可。但了解 MMU 有助于帮助我们理解操作系统,理解进程等,让我们对 嵌入式Linux 的理解上升一个层次。本文将简单地讲述一下关于 MMU 的基本信息。

注意:本文将按照ARMv7的二级页表映射进行讲述

二、MMU

2.1 MMU基本信息

MMU 全称为 Memory Management Unit,即 内存管理单元。在 带有MMU的嵌入式Linux 中,CPU 访问的地址都是 虚拟地址,而 MMU 负责将程序中 代码或数据虚拟地址 翻译为 物理地址,以便程序访问内存。

在执行操作时,MMU 会自动转换 CPU发出的虚拟地址,无法人工进行操作,只需要配置好 MMU 相关属性即可。

虚拟地址 是在 编译和链接 时定义的,可以简单地理解为 由链接器和链接器脚本 指定虚拟地址。
除了 翻译虚拟地址MMU 还可以配置 内存区域 的各项配置,如内存区域的访问权限内存区域是否使能cache等功能。
总结 MMU 的功能,如下:

  • 翻译虚拟地址
  • 配置内存区域的相关属性

2.2 MMU基本概念

看到 MMU 的相关文章时,总会提及几个概念如 页框(页帧)页表页表项TLB等等,下面我们逐个拆分来讲述。

2.2.1 页

MMU 管理 虚拟地址空间 时,是按照 为单位来进行管理。在 ARMv7MMU页大小 一共有 16M(Super Section)1M(Section)64K(Large Page) 4K(Page)页大小 可以通过 协处理器CP15 进行配置,越小的页意味着内存的颗粒度越小,内存使用时的浪费会越小,但也意味着使用的TLB行越多。越大的也内存的颗粒度月大,内存的使用浪费也可能月大,但使用的TLB行越少。比如只需要申请 7K 大小的 物理内存,如果使用 7K大小 的内存,我们可以分配 24K页,如果分配 64K的大页,则浪费的空间就比较大。

2.2.2 页框

因为 虚拟地址空间 需要有所对应的 物理地址,这样才能在 虚拟地址 中存储数据。所以 MMU 管理 物理地址空间 时,按照 页帧 为单位进行管理。其大小分为 64K4K。一段 虚拟地址空间 有可能存在着多个 ,这些 对应着多个 页帧

按照笔者理解,页帧不同地址空间下的关于内存空间大小的概念

2.2.3 页表及页表项

MMU 在进行 地址转换 时,需要一些信息,存放这些信息的就是 页表。每个 页表 的最小单位就是 页表项
页表 存储在 物理地址空间 中,且一个 页表项 对应着一个
切换页表 时,通过将 页表的物理首地址 设置到 协处理器CP15 中的 TTBR寄存器(Translation Table Base Register)。此后 MMU 会通过该地址自动去 物理地址空间 中找到对应的 页表,从而完成 虚拟地址到物理地址的映射

在不考虑 TLB多级页表 的情况下,可以简单地如下图所示:

  ARM体系架构——MMU【转】

 

 

页表及页表项

 

2.2.4 TLB

TLB 全程为 Translation Lookaside Buffer,即 旁路转换缓冲。它是 MMU 的专属 全相联cache,用于临时存放 虚拟地址到物理地址映射 所需要的信息。
下面按照步骤说明 TLB 的作用:

  1. CPU 访问 虚拟地址MMU
  2. MMU 根据规则(规则在下文讲述)查看 虚拟地址 是否在 TLB 中。
  3. 如果在 TLB 中,则称为 TLB命中。从 TLB 中直接获取 物理地址 对内存进行访问
  4. 如果不在 TLB 中,则称为 TLB失效。此时 MMU 将进行 translation table walking,即通过 访问页表来获取 物理地址。并将该 虚拟地址 的信息存入 TLB,以便下次使用。

值得注意的是:ARM架构的TLB只存储有效的页表项,对于无效的页表项TLB并不会存储

TLB 由许多 TLB行 组成,如下图所示:

  ARM体系架构——MMU【转】

 

 

TLB
TLB行3个 部分组成,分别为 标签ASID描述符

 

  • 标签:该部分由 虚拟地址的一部分bit 组成,MMU 通过将 虚拟地址的一部分bitTLB 的所有标签对比进行搜索。
  • ASID:全称为 Address Space ID,一般用于 多进程系统,下文会详细讲述。
  • 描述符:由 2个 部分组成,分别为 物理地址(一部分bit)内存区域属性 组成。可以理解为 cache 中的数据。

一般情况下,切换 进程 时会切换 页表,因为随着进程的切换, 虚拟地址物理地址 的映射已经改变。此时需要 清理TLB(即无效化TLB中的数据) 来保持 TLB一致性清理TLB 一般通过 协处理器CP15 来完成,在 Linux内核 中,有 flush_tlb_all()flush_tlb_range() 函数来完成该工作。

2.3 MMU组成

如下图所示:

  ARM体系架构——MMU【转】

 

 

MMU组成

MMU 的工作流程可以总结为下面 2 种情况:

  • 访问 虚拟地址 时,MMU 通过查找 TLB 来找出对应的 页帧,从而访问 物理地址,如图中的 页1页2页3
  • 如果 MMUTLB 中没找到对应的 TLB行 时,将进行 traslation table working。即从 物理地址空间页表中 找出对应的 页表项,并根据 页表项 找到对应的 物理地址。并将该 页表项 更新到 TLB 中,以备下次使用。

2.4 MMU工作过程

ARMv7 下的 MMU 具有 2级页表,分为 1级页表2级页表

2.4.1 1级页表

1级页表 也称 主页表段页表,下面简称 L1页表。它将 4GB 的地址空间划分为 40961MB 大小的 ,每个段的地址为 32bit。所以 1级页表 拥有 409632bit页表项

2.4.1.1 一级页表项

L1页表 使用了 短描述符页表(Short-descriptor translation table),其 页表项 具有以下特征:

  • 32bit 的页描述符
  • 具有 2级 以上的 页表
  • 支持 32bit物理地址
  • 支持 4种 内存大小:
    • 16MB/1M,称为
    • 64KB/4KB,称为

在前面说了 TTBR寄存器 是存放 页表物理地址 的寄存器,需要注意的是:存放在TTBR寄存器的地址需要16KB对齐

一级页表项 一共有 4种 格式,如下图所示:

  ARM体系架构——MMU【转】

 

 

一级页表项格式

 

每种格式都由 物理地址部分+属性部分 组成,可以直接在图中看出 物理地址部分 的示意,这里不多赘述。各种格式的含义如下:

  1. 1MB段转换页表项(Section) ,映射到 1MB 的物理地址范围。其 物理地址部分 即为所需要映射的 物理基地址
  2. 物理地址部分 指向 2级页表物理基地址
  3. 16MB段(SuperSection) 转换页表项,是一种特殊的 1MB段转换页表项。其 物理地址部分 即为所需要映射的 物理基地址
  4. 无效页表项,当访问该页表项时,将触发 指令取指异常取数据异常

下面简单说下各个字段的含义:

  • Ignored:忽略
  • Level 2 Descriptor Base Address二级页表物理基地址
  • Section Base Address1MB段基地址
  • Supersection Base Address16MB段基地址
  • SBZ:全称 should be zero,无效属性字段
  • AP:全称 Access Permissions,内存区域访问权限
  • Domain:用于权限控制,下文讲述。
  • TEX:全称 Type extension,设置内存区域类型
  • B:全称 Bufferable,是否设置 写缓冲
  • C:全称 Cacheable,是否设置 cache
  • nG:全称 non-Global。如果 页表项的nGbit 被设置,那么该 页表项 对应的 内存区域 将只能被 特定的进程 使用。当MMU 使用该 页表项 进行映射时,也需要使用到 ASID
  • S:全称 Shareable,共享设置项。
  • bit[18]:该 bit 决定 段页表项1MB页表项 还是 16MB页表项
  • bit[1:0]:这 2个bit 决定页表项的类型,如下:
    • 00无效页表项
    • 01转换表页表项
    • 10段页表项

2.4.1.2 一级页转换

1MB段 举例,假设 L1页表 的物理地址为 0x12300000,现在有一个虚拟地址 0x00100000。其转换过程如图所示:

  ARM体系架构——MMU【转】

 

 

查表过程
  ARM体系架构——MMU【转】

 

 

转换过程

 

  1. 查表过程:将 虚拟地址高12bit,即0x001 乘以 4 得到 0x0040x004 即为 该虚拟地址所在段的页表项在页表中的偏移,所以 该虚拟地址对应的页表项的物理地址为0x12300000+0x004=0x12300004
  2. 根据查到的 页表项,将 页表项高12bit虚拟地址低30bit 结合,即为 该虚拟地址在该1MB段内的物理地址

值得注意的是:例子中,高12位一共是4096个页表项,那么4096x4一共是16384字节的大小,因为每个页表项是32位。所以4096个页表项需要16K大小的内存来存储页表。也是因为如此,每个虚拟地址的高12bit都需要乘以4.

下图为例子的完整转换过程,其余类型的 页表项 转换过程类似、

  ARM体系架构——MMU【转】

 

 


 

 

L1转换完整过程

 

2.4.2 二级页表

2级页表 一共有 2564字节大小页表项,总共占据 1KB大小 的内存空间。L2页表 的大部分内容与 L1页表 类似,相同部分下文将不再赘述

2.4.2 二级页表项

二级页表项 一共有 3种 格式,如下图所示:

  ARM体系架构——MMU【转】

 

 

二级页表项

 

每种格式与 L1页表项 一样由 物理地址部分+属性部分 组成,可以直接在图中看出 物理地址部分 的示意,格式如下:

2级页表项 具有以下特征:

  1. 粗页表项: 其 物理地址部分指向 64KB大小物理基地址
  2. 细页表项: 其 物理地址部分指向 4KB大小物理基地址
  3. 无效页表项,当访问该页表项时,将触发 指令取指异常取数据异常

属性字段 的含义请参考 1级页表 章节。

2.4.2 二级页表转换

L2页表 的转换过程与 L1页表 的转换过程一脉相承。以 4KB 为例子,如下图所示:

  ARM体系架构——MMU【转】

 

 

image.png

 

由上图可以看出其转换步骤如下:

  1. 通过 虚拟地址 找出 L1页表项 并转换为 L2页表基地址
  2. 根据 L2页表基地址 并集合 虚拟地址的[19:12]bit 找出 虚拟地址 对应的 L2页表项
  3. 虚拟地址[11:0]bitL2页表项物理地址部分 结合得出具体的 物理地址

结合 L1页表 的完整转化过程如下图所示:

  ARM体系架构——MMU【转】

 

 

image.png

 

2.5 MMU内存属性

2.5.1 内存区域权限

每个 内存区域 都有自己的权限,不符合访问权限的 内存访问 都会引发 异常。如果是 数据访问 则引发 数据异常。如果是 指令访问,且该指令在执行前没有被 flush,将引发 预取指异常
引发的 异常原因 将会被设置在 CP15the fault address and fault status registers

内存区域权限APAPXDomain(域) 共同控制,如下:

  • AP/APX:字段 APAPX 的不同组合将形成不同的 访问权限,如图所示。按照笔者理解,
    Privileged 指的是 CPU 处于 svc 等状态,而 Unprivileged 则为 CPU 处于 user 状态。
      ARM体系架构——MMU【转】

     

     

    访问权限组合表
  • Domain:这是一种 ARM架构 不常用的 内存全权限控制 方式。MMU 可以将所有 内存区域 分配到 16个域 中,每一个 都有自己的 访问权限。被分配到 中的 内存区域 必须遵循该 的访问权限。可以通过设置 页表项 中的 Domain字段 来实现 的分配。
    CP15协处理器 中有一个 DACR寄存器(Domain Access Control Register),该寄存器为 32bit,每个 访问权限2个bit 设置,一共设置 16个域 的权限设置如下:
    • 不可访问(no-access):对该 内存区域 进行访问将引发 异常
    • 管理者(Manager mode):访问不受任何控制,不产生 异常
    • 用户(Client mode):使用 页表项 中的 AP/APX字段 进行控制

需要注意的是:内存区域控制以域控制为主,页表项的AP/APX字段为次。ARMv7不建议使用域进行控制,所以建议把DACR寄存器设置为用户模式

2.5.2 内存类型

ARM架构 实现了 3种内存类型,每种类型都是 互斥的,如下:

  • Strongly-ordered
  • Device
  • Normal

每种 类型 的细节如下图所示:

  ARM体系架构——MMU【转】

 

 

image.png

 

需要注意的是,Device类型的Shareable内存区域现在已经被弃用

内存区域类型 可以通过 TEX字段C字段B字段 来进行设置,如下图所示

  ARM体系架构——MMU【转】

 

 

image.png

 

值得注意的是:按照笔者理解,inner cache是L1 cache,而outer cache是指在L1cache下面的cache,比如L2cache

操作系统如何使用页表

2.6 进程与MMU

操作系统 会为 每个进程 分配一个 页表,该 页表 使用 物理地址 存储。当 进程 使用类似 malloc 等需要 映射代码或数据 的操作时,操作系统 会在随后马上 修改页表 以加入新的 物理内存。当进程完成退出时,内核会将相关的页表项删除掉,以便分配给新的进程。

2.6.1 Address Space ID

在操作系统中, 多进程 是一种常态。那么多进程 的情况下,每次 切换进程 都需要进行 TLB清理。这样会导致切换的效率变低。
为了解决问题,TLB 引入了 ASID(Address Space ID)ASID 的范围是 0-255
ASID 由操作系统分配,当前进程的ASID值 被写在 ASID寄存器(使用CP15 c3访问)TLB 在更新 页表项 时也会将 ASID 写入 TLB
如果设置了如果 当前进程的ASID,那么 MMU 在查找 TLB 时, 只会查找 TLB 中具有 相同ASID值TLB行。且在切换进程是,TLB 中被设置了 ASIDTLB行 不会被清理掉,当下次切换回来的时候还在。所以ASID 的出现使得切换进程时不需要清理 TLB 中的所有数据,可以大大减少 切换开销

具体可以看参考链接《多核MMU和ASID管理逻辑》

2.6.2 TTBR0和TTBR1

前面讲了 TTBR寄存器 是用于存放 页表基地址,在 ARmv7 中一共有 2个 这样的寄存器,分别是 TTBR0TTBR1
那么这里提出一个问题:在进行 Translation Table walking 的时候,选择哪个TTBR寄存器,又如何选择?
ARMv7 中,有一个寄存器为 TTBCR(TTB Control Register),即TTB控制寄存器TTBCR寄存器 可以被设置为 0-7 这几个值。
在进行 地址映射 时, MMU 会根据 TTBCR寄存器 中的值查看 虚拟地址 是高位地址,根据 高位地址 选择对应的 TTBR寄存器
举个例子,假设 TTBCR寄存器 被设置为 4,则 MMU 会检查 虚拟地址高4bit,如果 高4bit 都为 0,则此时选择 TTBR0

需要注意的是:TTBCR被设置为 0 时,默认选择 TTBR0。

下面我们看看使用和不使用 TTBR1 带来的影响。

  • 不使用:在 ARM32架构的操作系统中,不使用 TTBR1寄存器。此时,用户空间内核空间 共用一个 页表。也就是说 用户空间内核空间 都使用 TTBR0 来记录 页表地址,这样可以避免一个问题,就是进行 用户空间和内核空间的切换时,可以避免切换页表带来的性能损耗。但与此同时也带来一个问题,用户空间每个进程 都拥有 内核页表副本,当 内核空间页表 修改时,所有 进程 都需要同步修改其 内核页表副本。造成一定的性能损失
  • 使用ARM64架构的操作系统中虚拟地址空间 非常大。用户空间内核空间 都是 256T用户空间地址高位为0,内核空间地址高位为1。这样的特性满足 TTBR1寄存器 的使用条件。根据 用户空间地址内核空间地址 的不同,选择对应的 TTBR寄存器。这样就不需要为每个 进程 维护一份 内核页表副本

2.6.3 代码实例

本小节简单地讲述一下 Linux 进行 MMU切换 时的代码片段。以 ARMv7单核CPU 为例子。
根据笔者的理解,其调用图谱如下:

->switch_mm
  ->check_and_switch_context
    ->cpu_switch_mm(processor.switch_mm)
      ->cpu_v7_switch_mm

笔者会将简单的说明注释在代码中,不进行另外的说明。

/* arch/arm/include/asm/mmu_context.h */
static inline void
switch_mm(struct mm_struct *prev, struct mm_struct *next,
      struct task_struct *tsk)
{
#ifdef CONFIG_MMU
    unsigned int cpu = smp_processor_id();

    /*
     * __sync_icache_dcache doesn't broadcast the I-cache invalidation,
     * so check for possible thread migration and invalidate the I-cache
     * if we're new to this CPU.
     */
    /* 这里应该是说进程如果调度到新的CPU,则需要将该CPU的cache给清理掉 */
    if (cache_ops_need_broadcast() &&
        !cpumask_empty(mm_cpumask(next)) &&
        !cpumask_test_cpu(cpu, mm_cpumask(next)))
        __flush_icache_all();

    if (!cpumask_test_and_set_cpu(cpu, mm_cpumask(next)) || prev != next) {
        /* 如果调度的进程不是本进程,则执行check_and_switch_context */
        check_and_switch_context(next, tsk);
        if (cache_is_vivt())
            cpumask_clear_cpu(cpu, mm_cpumask(prev));
    }
#endif
}

static inline void check_and_switch_context(struct mm_struct *mm,
                        struct task_struct *tsk)
{
    if (unlikely(mm->context.vmalloc_seq != init_mm.context.vmalloc_seq))
        __check_vmalloc_seq(mm);

    if (irqs_disabled())
        /*
         * cpu_switch_mm() needs to flush the VIVT caches. To avoid
         * high interrupt latencies, defer the call and continue
         * running with the old mm. Since we only support UP systems
         * on non-ASID CPUs, the old mm will remain valid until the
         * finish_arch_post_lock_switch() call.
         */
        mm->context.switch_pending = 1;
    else
        /* 使用该函数进行MMU切换页表 */
        cpu_switch_mm(mm->pgd, mm);
}

/* arch/arm/include/asm/proc-fns.h */
/* 根据笔者找的代码,cpu_switch_mm 应该直接调用了processor.switch_mm */
#define cpu_do_switch_mm        processor.switch_mm
#define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm)

processor.switch_mm 是一个 回调函数,根据笔者找到的资料,应该是指向 ** arch/arm/mm** 目录下的一些列 MMU 操作代码。这里以 proc-v7-2level.S(即ARMv7 2级页表) 进行说明

/* arch/arm/mm/proc-v7-2level.S */
/* 根据APCS,传入的参数是存放在寄存器 r0和r1 */
ENTRY(cpu_v7_switch_mm)
#ifdef CONFIG_MMU
        mmid    r1, r1                          @ get mm->context.id
        ALT_SMP(orr     r0, r0, #TTB_FLAGS_SMP)
        ALT_UP(orr      r0, r0, #TTB_FLAGS_UP)
#ifdef CONFIG_PID_IN_CONTEXTIDR
        mrc     p15, 0, r2, c13, c0, 1          @ read current context ID
        lsr     r2, r2, #8                      @ extract the PID
        bfi     r1, r2, #8, #24                 @ insert into new context ID
#endif
#ifdef CONFIG_ARM_ERRATA_754322
        dsb
#endif
        mcr     p15, 0, r1, c13, c0, 1          @ set context ID
        isb
        /* 在这里,将r0所指向的页表基地址设置到TTBR0中,完成页表的切换 */
        mcr     p15, 0, r0, c2, c0, 0           @ set TTB 0
        isb
#endif
        bx      lr
ENDPROC(cpu_v7_switch_mm)

三、参考链接

《ARM Cortex-A Series Programmer’s Guide》
《Cortex-A7 MPCore Technical Reference Manual》
《多核MMU和ASID管理逻辑》
TLB的作用及工作过程
MMU和cache详解(TLB机制)
inux-kernel – Linux内核ARM转换表库(TTB0和TTB1)
ARM TTBR0,TTBR1寄存器与ARM32页表复制
选择使用TTBR0或TTBR1做为translation table base地址寄存器
TLB中ASID和nG bit的关系
ASID
Linux arm 进程切换
ARM-LINUX的进程切换



作者:wipping的技术小栈
链接:https://www.jianshu.com/p/ef1e93e9d65b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
上一篇:【强化学习】在gym环境下,*的算法总结


下一篇:基于QT5的libmodbus主从机实现