这是一个非常好的问题,触及了ARM架构(特别是ARMv8-A/ARMv9-A)最核心的内存管理机制。

简单直接的回答是:物理上通常只有一个MMU硬件,但逻辑上每个异常级别都有自己的“一套”控制寄存器。

你提到的“用户层MMU”和“内核层MMU”的区分,实际上是通过寄存器配置地址空间划分来实现的,而不是真的有多个独立的物理MMU部件。

下面我为你详细拆解这个机制:

1. 到底有几套 MMU?

在ARMv8架构中,有4个异常级别:EL0(用户)、EL1(内核)、EL2(Hypervisor)、EL3(Secure Monitor)。

物理层面:
一个CPU核心通常只有一个物理MMU转换部件。它负责将虚拟地址(VA)转换为物理地址(PA)。

逻辑/寄存器层面:
为了支持不同特权级别的隔离,ARM为每个异常级别都配备了一组独立的系统控制寄存器。你可以理解为有“四套配置面板”,但只有一个“执行引擎”。

这几套配置寄存器如下:

异常级别 角色 对应的MMU控制寄存器 说明
EL3 Secure Monitor TTBR0_EL3, TCR_EL3 最高特权,管理安全世界的内存映射。
EL2 Hypervisor TTBR0_EL2, TCR_EL2 管理虚拟化扩展,控制客户机的内存。
EL1 OS Kernel TTBR0_EL1, TTBR1_EL1, TCR_EL1 最关键的一套,内核和用户空间都在这里管理。
EL0 Application 无独立寄存器 用户层没有配置寄存器,它“借用”EL1的配置进行转换。

2. 怎么区分用户层和内核层的 MMU?

你感到困惑的地方通常在于 EL1(内核)和 EL0(用户)的关系。既然EL0没有寄存器,它是怎么工作的?

核心机制:TTBR0 与 TTBR1 的“顶半部/底半部”划分

ARM在设计EL1时,引入了一个非常巧妙的机制:一个MMU硬件,两张页表基址寄存器

  • TTBR0_EL1 (Translation Table Base Register 0):通常用于用户空间
  • TTBR1_EL1 (Translation Table Base Register 1):通常用于内核空间

硬件通过虚拟地址的最高位(具体位数由 TCR_EL1 寄存器配置决定)来自动选择使用哪张页表:

工作流程示意图:

假设我们配置地址空间划分为高位内核,低位用户:

  1. 当 CPU 运行在 EL0 (用户态) 时:

    • 用户程序发出的虚拟地址通常是低位地址(例如 0x0000_7FFF_XXXX)。
    • MMU 看到高位是 0,于是自动选择 TTBR0_EL1 指向的页表进行查表。
    • 结果:进程只能访问自己的用户空间内存。
  2. 当 CPU 发生异常陷入 EL1 (内核态) 时:

    • 内核代码通常运行在高半部地址(例如 0xFFFF_8000_XXXX)。
    • MMU 看到高位是 1,于是自动选择 TTBR1_EL1 指向的页表进行查表。
    • 结果:内核访问自己的全局映射。
  3. 内核访问用户数据时:

    • 内核有时候需要拷贝用户数据。此时内核虽然处于 EL1,但它故意构造一个低位虚拟地址。
    • MMU 看到高位是 0,依然会去查 TTBR0_EL1
    • 关键点:虽然内核在查用户页表,但因为它运行在 EL1,它有权限访问,而用户进程(EL0)如果试图访问高位地址(内核区域),MMU会直接报错。

3. 为什么这样设计?

这种设计解决了传统架构(如ARMv7或x86早期)的一个痛点:

  • 无需频繁重载页表基址:
    在旧架构中,进入内核需要切换 CR3(页表基址),这会导致 TLB(快表)被清空,性能损失巨大。
    在ARMv8中,内核页表(TTBR1)永远驻留。从用户态陷入内核态时,不需要切换寄存器,只需要切换权限等级,TLB中关于内核的映射依然有效,大大加速了系统调用和中断处理的速度。

4. 总结:如何区分?

回到你的问题,区分方法如下:

  1. 不同异常级别(EL3/EL2/EL1)的区分:
    物理切换。当CPU从EL1切换到EL2时,硬件会自动使用 TTBR0_EL2 而不再是 TTBR0_EL1。这是完全隔离的,每个级别都有自己独立的控制寄存器组。

  2. 同级别内 用户 vs 内核(EL0 vs EL1)的区分:
    地址范围划分

    • 高地址 -> 自动路由到 TTBR1_EL1 (内核页表)。
    • 低地址 -> 自动路由到 TTBR0_EL1 (用户页表)。
    • EL0 进程切换时,OS 只需要更新 TTBR0_EL1 的值,而 TTBR1_EL1 保持不变。

所以,EL3、EL2、EL1(内核部分) 各有一套独立的MMU配置,而 EL0(用户) 是依附在 EL1 的 TTBR0 机制下的

这是一个非常核心的问题。在ARMv8架构中,MMU的控制寄存器组(SCTLR, TCR, TTBR, MAIR等)就像是一个复杂的“控制面板”,它们共同决定了CPU如何看到内存。

我们可以将这些控制功能分为五大类:总开关、地址映射规则、页表存放位置、内存属性定义、以及权限控制

以下是详细的拆解:

1. 总开关与全局控制 (SCTLR_ELx)

SCTLR (System Control Register) 是最高层的控制寄存器,它决定了MMU是否工作。

  • M 位MMU使能位
    • 1:开启MMU,所有地址访问都经过翻译。
    • 0:关闭MMU,虚拟地址直接等于物理地址(平坦映射)。
  • C 位:数据缓存使能。
  • I 位:指令缓存使能。
  • A 位:对齐检查。如果开启,非对齐的内存访问会触发异常。

2. 地址映射规则与范围 (TCR_ELx)

TCR (Translation Control Register) 是配置最复杂的寄存器,它定义了“如何翻译地址”。它决定了你之前问的“用户/内核空间划分”。

  • T0SZ / T1SZ (Translation Size)
    • 功能:控制地址空间的范围大小。
    • 应用:这是区分用户和内核的关键。
      • T0SZ 决定了 TTBR0(用户空间)映射多大的地址范围(例如 48位地址空间)。
      • T1SZ 决定了 TTBR1(内核空间)映射多大的地址范围。
      • 通过这两个值,硬件计算出分界线(例如高 16位是全1则用TTBR1,否则用TTBR0)。
  • TG0 / TG1 (Translation Granule)
    • 功能:设置页块大小。
    • 应用:决定页表粒度是 4KB、16KB 还是 64KB。这对性能和内存碎片管理有直接影响。
  • IPS / PS (Intermediate Physical Size)
    • 功能:设置输出的物理地址位宽。
    • 应用:告诉MMU你的物理内存有多大(例如36位、40位或48位),防止翻译出超出物理总线范围的地址。
  • SH0 / SH1 (Shareability)
    • 功能:设置页表遍历时的共享属性。
    • 应用:决定页表本身是Inner Shareable还是Outer Shareable,影响多核CPU的缓存一致性。

3. 页表存放位置 (TTBRx_ELx)

TTBR (Translation Table Base Register) 是最直观的寄存器,它告诉MMU“字典”放在哪里。

  • BADDR (Base Address)
    • 功能:存放一级页表(L0 Table)的物理基地址。
  • ASID (Address Space Identifier)
    • 功能:进程上下文标识符。
    • 应用:这是区分不同进程的关键。当CPU切换进程时,OS会修改这里的ASID。TLB(快表)在缓存翻译结果时,会打上ASID的标签。这样切换进程时不需要清空整个TLB,只需要匹配ASID即可,极大提升性能。

4. 内存属性定义 (MAIR_ELx)

MAIR (Memory Attribute Indirection Register) 是一个“属性调色板”。因为页表项(Descriptor)里的位宽有限,不能存下所有的属性细节,所以使用了索引机制。

  • Attr[n] (8个字节属性组)
    • 功能:定义具体的内存类型,例如:
      • Normal Memory:普通内存,支持缓存。
      • Device Memory:设备内存(如外设寄存器),不支持缓存,必须严格顺序访问。
      • Cacheability:Write-Back (回写) 还是 Write-Through (直写)。
    • 应用:页表项里只需存一个索引号(如 AttrIdx=0),MMU就会去查 MAIR 的第0段,得知这块内存是“带缓存的普通内存”。

5. 权限控制 (在页表描述符中)

虽然具体的读写权限位主要存放在页表项中,但控制寄存器组定义了权限检查的基准规则

  • 页表项中的 AP (Access Permissions)
    • 控制读/写权限。
    • 控制特权级访问权限(EL0可访问,还是仅EL1可访问)。这就是区分“用户空间”和“内核空间”数据的根本屏障。
  • PXN (Privileged Execute Never) / XN (Execute Never)
    • 功能:执行权限控制。
    • 应用:防止内核执行用户空间代码(防止ret2usr攻击),或者防止执行数据区代码(防止栈溢出攻击)。

总结:一个完整的控制流程

当CPU发起一次地址访问时,MMU控制寄存器组协同工作的流程如下:

  1. SCTLR 说:“MMU是开的,开始干活。”
  2. TCR 看了一下虚拟地址的高位:“这个地址高位是1,属于内核空间,去查 TTBR1;那个地址高位是0,属于用户空间,去查 TTBR0。”
  3. TTBR 给出页表基址:“页表在物理内存的 0x8000_0000 处。”
  4. MMU 开始遍历页表,遇到页表项里的属性索引。
  5. MAIR 说:“索引0对应的是‘普通内存、可缓存’,那就把数据放入Cache吧。”
  6. 页表项里的 AP位 检查:“当前是EL0(用户态),想写这块内存?不行!权限不够,触发异常!”

通过这套寄存器组,ARM实现了极其灵活且安全的内存管理。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐