资讯动态

ARM内存管理三剑客:MPU、MMU、SMMU的区别与协同

发布时间:2026/10/13 5:30:52 来源:尧图企业网站定制
1. 从一块开发板的启动日志说起三种内存管家到底谁在管谁第一次在串口终端里看到MMU enabled这行打印的时候我盯着屏幕愣了几秒——明明前一秒代码还在按物理地址跑后一秒地址空间就换了个世界。后来做带 DMA 的外设驱动又撞上了SMMU translation fault日志里一串 IOVA 地址看得人头皮发麻。再往后接触实时性要求高的场景MPU 又冒了出来配置粒度粗、没有页表、但胜在确定性极强。这三个词——MPU、MMU、SMMU——在 ARM 体系里经常被放在一起讨论但它们解决的问题、所处的层级、以及谁为谁服务的关系很多人其实是模糊的。我见过不少做了两三年驱动的同行能熟练写mmap、能配iommu_map但被问到MPU 和 MMU 能不能同时开SMMU 的页表是谁建的时回答就开始含糊。这篇内容就是想把这三者彻底拆开讲清楚。它适合正在做 ARM 平台底层开发、驱动移植、虚拟化或者异构计算的工程师也适合刚接触 Cortex-A/R/M 系列、被地址翻译绕晕的朋友。我会从它们各自要解决的核心问题切入讲清楚硬件层面的工作方式再落到实际配置和踩坑经验上。核心关键词就三个MPU、MMU、SMMU外加它们共同依赖的地址翻译与内存保护机制。先把一句话结论摆在这里方便你建立坐标系MPU 管的是这块内存能不能访问MMU 管的是这个虚拟地址映射到哪块物理内存SMMU 管的是外设发出的地址怎么翻译成物理地址。三者不是替代关系而是分别站在 CPU 核内、CPU 核内、以及系统总线上的三个不同位置各管一摊。2. MPU没有页表的粗粒度门卫为什么实时系统偏爱它2.1 MPU 的本质区域描述符而非页表MPU 全称 Memory Protection Unit内存保护单元。它和 MMU 最大的区别在于MPU 不做地址翻译只做访问权限检查。CPU 发出的地址就是物理地址MPU 只是站在旁边看一眼——这个地址落在哪个区域region里当前的特权级别和访问类型读/写/执行允许吗允许就放行不允许就触发 MemManage fault。它的配置模型是区域描述符每个 region 用一组寄存器描述基地址、大小、权限、是否可缓存、是否可共享等属性。ARMv7-M比如 Cortex-M3/M4/M7通常支持 8 个 regionARMv8-M 支持 16 个并且可以给每个 region 配子区域sub-region做更细的禁用控制。这里有个很多人第一次配 MPU 会踩的坑region 的大小必须是 2 的幂且基地址必须按大小对齐。比如你想保护 0x20000000 开始的 64KB SRAM那 base 必须是 0x20000000size 选 64KB没问题。但如果你想保护一段 48KB 的区域对不起MPU 没有这个粒度你只能拆成 32KB 16KB 两个 region或者干脆用 64KB 覆盖再靠 sub-region 关掉多余部分。2.2 为什么 RTOS 和裸机场景更愿意用 MPUMMU 需要页表、需要 TLB、需要页表遍历page table walk这一套下来地址翻译的延迟是不确定的——TLB 命中快未命中就要走内存访问页表最坏情况下的延迟对硬实时系统是灾难。MPU 没有这个问题权限检查是组合逻辑固定周期确定性极强。所以在 RTOS比如常见的实时操作系统里MPU 常被用来做任务隔离每个任务有自己的栈和私有数据区MPU 配置成任务切换时只允许访问自己的区域一旦越界立刻 fault把野指针踩到别人数据这种问题在发生瞬间就抓住而不是等到系统跑飞了再回头查。我实际做过的一个项目里用 MPU 把几个任务的数据区隔开结果在一次联调中直接抓到了一个任务因为数组越界写坏了另一个任务控制块的 bug。如果没有 MPU这个 bug 会表现为某个不相关的任务偶尔行为异常排查成本可能是几天有了 MPUfault 现场直接指向越界的那条指令半小时定位。2.3 MPU 配置的实操要点与常见误区配置 MPU 的典型流程是这样的以 ARMv7-M 为例关闭 MPUMPU-CTRL 0确保配置过程中不会触发意外 fault。选择要配置的 region 编号写入MPU_RBAR基地址 region 号 有效位。写入MPU_RASR大小、权限、属性、子区域禁用位、使能位。重复以上步骤配置所有 region。使能 MPU并根据需要使能背景区域background region。/* 以常见的 CMSIS 风格为例配置一段只读可执行的 Flash 区域 */ MPU-RNR 0; /* 选择 region 0 */ MPU-RBAR 0x08000000 | (1UL 4); /* 基地址VALID 位置 1 */ MPU-RASR (0x05UL 1) /* size: 32KB编码需查手册 */ | (0x06UL 24) /* AP: 特权可读写用户只读 */ | (0x01UL 28); /* XN0允许执行 */几个必须注意的点region 重叠时的优先级高编号 region 的权限会覆盖低编号 region。这个规则很多人不知道导致配了半天发现权限不对其实是后面的 region 把前面的覆盖了。背景区域如果使能了 background region那么没有被任何 region 覆盖的地址会使用默认内存映射的权限如果不使能未覆盖区域的访问会直接 fault。这个开关决定了你的默认策略是宽松还是严格。栈的保护给栈区域配一个紧邻的守卫 region权限设为不可访问栈溢出时会立刻触发 fault这是 MPU 最实用的技巧之一。提示MPU 的 region 数量有限别指望用它做精细的内存管理。它的定位是粗粒度隔离 确定性保护把最关键的几段内存栈、关键数据、外设寄存器圈起来就够了。3. MMU页表驱动的地址翻译虚拟内存的硬件基石3.1 MMU 解决的核心问题虚拟地址到物理地址的映射MMU 全称 Memory Management Unit。如果说 MPU 是门卫MMU 就是翻译官 门卫二合一。CPU 核发出的所有地址都是虚拟地址VAMMU 负责把它翻译成物理地址PA翻译的同时顺带做权限检查。翻译的依据是页表。页表是一棵多级树ARMv8-A 常见的是 4 级页表Level 0 到 Level 3每一级把地址的一部分作为索引逐级往下找最后找到页表项PTE里面存着物理页帧号PFN和权限属性。以 4KB 页为例48 位虚拟地址被切成 999912 的结构前四段各索引一级页表最后 12 位是页内偏移。这个多级结构的好处是省内存不需要为整个地址空间建一张大表只在实际用到的区域建分支。代价是翻译延迟——一次未命中的翻译要访问 4 次内存每级页表一次。所以硬件加了TLBTranslation Lookaside Buffer做缓存把最近用过的 VA→PA 映射缓存起来命中就一步到位。3.2 页表项里到底存了什么一个典型的 ARMv8-A 页表项描述符包含这些关键字段字段作用输出地址下一级页表基址或最终物理页帧号AFAccess Flag是否被访问过供操作系统做页面回收APAccess Permission读写权限、特权/用户态区分UXN/UXN用户态是否可执行PXN特权态是否可执行AttrIndx指向 MAIR 寄存器决定内存属性Normal/Device、可缓存性Shareability是否可共享多核一致性相关Valid描述符是否有效这里面的AttrIndx MAIR组合是很多人配 MMU 时最容易出错的地方。MAIRMemory Attribute Indirection Register是一张属性表页表项里的 AttrIndx 只是个索引真正的内存类型比如 Normal Write-Back、Device nGnRnE存在 MAIR 里。如果你把设备寄存器的页配成了 Normal 可缓存那读写外设就会出现读到的值不对写下去没生效这类诡异问题——因为缓存把访问拦截了。3.3 多级页表遍历与 TLB 维护的实操经验配 MMU 的完整流程大致是在内存里分配页表空间按架构要求对齐比如 4KB 对齐。填写各级页表项建立 VA→PA 映射设置权限和属性。配置 MAIR、TCRTranslation Control Register定义页表级数、粒度、地址范围。把页表基址写入 TTBR0_EL1用户空间和 TTBR1_EL1内核空间。配置 SCTLR_EL1 使能 MMU 和缓存。执行ISB、TLBI等指令刷新流水线和 TLB。/* 使能 MMU 前后的关键操作AArch64 简化示意 */ msr ttbr0_el1, x0 // 写入用户空间页表基址 msr ttbr1_el1, x1 // 写入内核空间页表基址 msr mair_el1, x2 // 配置内存属性表 msr tcr_el1, x3 // 配置翻译控制 dsb sy tlbi vmalle1 // 清空 TLB dsb sy isb mrs x4, sctlr_el1 orr x4, x4, #1 // 置位 M 位使能 MMU msr sctlr_el1, x4 isb踩过的坑里最典型的是改完页表忘记刷 TLB。页表改了但 TLB 里还缓存着旧映射CPU 继续用旧地址翻译表现为代码明明改了却没生效。ARM 提供了多种 TLBI 粒度tlbi vmalle1清全部tlbi vae1按 VA 清tlbi aside1按 ASID 清。性能敏感的场景要按需选择别动不动就全清。另一个坑是页表对齐和属性一致性。页表本身所在的内存必须是 Normal 可缓存且一致的否则多核场景下会出现一个核改了页表、另一个核看不到的情况。这个在 SMP 系统里尤其要注意。注意MMU 使能的瞬间PC 所在的代码、当前栈、以及接下来要访问的所有数据都必须已经在页表里有正确映射。否则第一条取指就会 fault。所以通常的做法是恒等映射identity mapping一段启动代码先让 MMU 跑起来再逐步切换到完整的虚拟地址布局。4. SMMU站在总线上的翻译官让外设也懂虚拟地址4.1 为什么外设需要自己的 MMUCPU 有了 MMU进程看到的是虚拟地址物理内存被保护得很好。但外设DMA 控制器、网卡、GPU、加解密引擎做 DMA 时发出的是物理地址——它们绕过 MMU直接怼到内存上。这就带来两个问题一是驱动要拿到物理地址才能让外设工作虚拟内存的隔离优势在外设这里失效二是恶意或有 bug 的设备可以 DMA 到任意物理内存把内核数据写坏。SMMUSystem Memory Management Unit就是来解决这个问题的。它挂在系统总线上拦截外设发出的地址IOVAI/O Virtual Address用一套独立的页表把它翻译成物理地址同时做权限检查。这样驱动就可以给外设分配虚拟地址外设用 IOVA 做 DMASMMU 负责翻译和隔离。在 ARM 体系里SMMU 的规范主要有 SMMUv1/v2对应 ARMv7/v8 早期和 SMMUv3对应 ARMv8重新设计了架构支持更高效的命令队列和事件队列。PCIe 场景下SMMU 还和 ATSAddress Translation Services、PASIDProcess Address Space ID配合实现设备侧的地址翻译缓存。4.2 Stream Table 与页表SMMU 怎么知道该用哪套映射SMMU 的核心数据结构是Stream Table。每个发起 DMA 的设备更准确说是每个 Stream ID通常由设备 ID 派生在 Stream Table 里对应一个表项表项指向该设备的翻译配置——用哪套页表、什么属性、是否 bypass。翻译流程大致是设备发出带 Stream ID 的 DMA 请求。SMMU 用 Stream ID 索引 Stream Table找到对应的配置。用配置里的页表基址对 IOVA 做多级页表遍历和 MMU 类似。得到物理地址做权限检查通过则访问内存不通过则产生 translation fault 并记录到事件队列。/* Linux 下典型的 SMMU 映射流程简化示意 */ struct iommu_domain *domain iommu_domain_alloc(platform_bus_type); iommu_attach_device(domain, dev); /* 分配 IOVA 并建立映射 */ phys_addr_t pa virt_to_phys(buffer); iommu_map(domain, iova, pa, size, IOMMU_READ | IOMMU_WRITE); /* 把 IOVA 告诉设备设备用它做 DMA */ writel(iova, dev_base DMA_ADDR_REG);4.3 SMMU 调试中最让人头疼的几类 fault做 SMMU 相关开发最常打交道的不是配置而是 fault。日志里出现SMMU translation fault时排查思路要清晰fault 地址是 IOVA 还是 PA先确认设备发出的是不是 IOVA。如果驱动错误地把物理地址当 IOVA 传给了设备SMMU 会拿物理地址去查页表大概率查不到报 fault。Stream ID 对不对设备树或 ACPI 里描述的 Stream ID 必须和硬件实际发出的一致。配错了SMMU 会用错误的页表翻译或者直接 bypass。页表项权限够不够设备要写但映射只给了读权限一样 fault。映射建立和 DMA 发起的时序映射还没建好设备就开始 DMA必然 fault。驱动里要保证iommu_map完成后再启动设备。我遇到过一次很隐蔽的问题设备做 64 位 DMA但 SMMU 的页表只配了 32 位地址范围高位地址被截断翻译到了错误的物理页数据写飞。这种问题不会立刻 fault而是表现为数据偶尔错乱排查起来非常费劲。后来在 Stream Table 配置里把地址范围补全才解决。提示SMMU 的 bypass 模式Stream Table 表项直接标记为 bypass会让设备 DMA 直通物理内存调试阶段临时用来排除 SMMU 因素很有用但生产环境千万别留 bypass否则隔离形同虚设。5. 三者协同一个 SoC 里它们如何各司其职5.1 从 CPU 到外设的完整地址路径把三者放到一个典型的 ARM SoC 里看地址路径是这样的CPU 核内程序发出 VAMMU 翻译成 PA或直接用 PA如果 MMU 关闭。MMU 的权限检查失败触发 abort。实时核 / 裸机核可能用 MPU 做保护地址不翻译直接是 PAMPU 只查权限。系统总线上外设发出 IOVASMMU 翻译成 PA权限检查失败触发 SMMU fault。所以一个数据从 CPU 写到外设可能经历MMU 翻译 SMMU 翻译两次地址转换如果外设也走 SMMU。这也是为什么在虚拟化场景下会出现两级翻译Guest VA → Guest PAStage 1由 Guest 的 MMU 管→ Host PAStage 2由 Hypervisor 的 MMU 管。SMMU 同样支持两级翻译对应设备的 Stage 1 和 Stage 2。5.2 MPU 与 MMU 能否共存这是被问得最多的问题之一。答案是在同一个核上MPU 和 MMU 通常不会同时使能。Cortex-A 系列一般只有 MMUCortex-M 系列一般只有 MPU部分高端 M 系列如 Cortex-M7 有可选 MPU但没有 MMUCortex-R 系列则可能两者都有但实际使用中通常二选一。原因在于它们的设计目标不同MMU 面向富操作系统需要虚拟内存、按需分页、进程隔离MPU 面向实时和裸机需要确定性、低开销。硬要把两者叠在一起权限检查逻辑会变得复杂收益也不明显。所以选型时先看你的场景要跑 Linux 这类富 OS用 MMU要跑 RTOS 或裸机且强调确定性用 MPU。5.3 虚拟化场景下的两级翻译与 SMMU 的配合在虚拟化环境里三者的关系会更复杂一层。Hypervisor 用 Stage 2 翻译把 Guest 的物理地址再翻译一层MMU 和 SMMU 都要支持两级翻译。这时候 SMMU 的 Stream Table 表项里会同时指向 Stage 1 和 Stage 2 的页表配置。实际配置时要注意Stage 1 由 Guest OS 管Stage 2 由 Hypervisor 管两者的页表格式和属性要匹配。如果 Guest 把某段内存配成 Device 属性而 Stage 2 配成 Normal 可缓存最终生效的属性可能出乎意料。这种跨层级的属性冲突是虚拟化平台调试中的经典难题。场景使用的单元地址类型典型用途裸机 / RTOSMPU物理地址任务隔离、栈保护富 OSLinux 等MMU虚拟地址进程隔离、按需分页外设 DMASMMUIOVADMA 隔离、虚拟化直通虚拟化 GuestMMU SMMUVA→IPA→PA两级翻译6. 配置与调试中的实战心得6.1 先确认谁在翻译再动手配我见过太多人一上来就翻手册配寄存器结果方向就错了。正确的顺序是先搞清楚当前场景下地址是谁翻译的。跑 Linux 的用户态程序地址翻译是 MMU 的事你改 MPU 没用写 DMA 驱动发现数据不对先确认设备走不走 SMMU不走的话查 cache 一致性走的话查 IOVA 映射。一个实用的判断方法看 fault 的类型和来源。Data abort来自 MMUMemManage fault来自 MPUSMMU translation fault来自 SMMU。日志里 fault 的名字直接告诉你该查哪套机制。6.2 内存属性配置的一致性检查清单内存属性可缓存性、共享性、设备属性是三者共同的坑点。我整理了一个检查清单配完内存映射后逐条过一遍设备寄存器区域是否配成了 Device 属性不可缓存、强序DMA 缓冲区是否配成了 Normal Non-cacheable 或做了 cache 维护多核共享的数据是否配了正确的 Shareability页表本身所在内存是否是一致可缓存的SMMU 的 Stage 1 和 Stage 2 属性是否冲突这几条里任何一条出问题表现都可能是数据偶尔不对这种最难查的症状。6.3 从 fault 日志反推配置错误fault 日志是最好的老师。以 SMMU fault 为例一条完整的 fault 记录通常包含fault 类型translation/permission/access、fault 地址、Stream ID、是否读写。拿到这些信息排查路径就清晰了用 Stream ID 找到对应设备确认它的页表配置。用 fault 地址在页表里查看是否有映射。有映射就查权限没映射就查为什么没建。确认设备发出的地址确实是 IOVA而不是被错误传入的 PA。MMU 的 abort 日志类似会给出 FARFault Address Register和 ESRException Syndrome RegisterESR 里的 DFSC 字段直接告诉你是什么类型的 fault翻译 fault、权限 fault、对齐 fault 等。学会读这些寄存器比盲目改配置高效得多。6.4 性能与确定性的取舍最后说一个选型层面的经验。MMU 和 SMMU 带来灵活性的同时也带来了 TLB miss 和页表遍历的开销。对延迟敏感的场景可以考虑用大页2MB 或 1GB减少页表级数和 TLB 压力。把关键代码和数据的映射锁定在 TLB 里如果硬件支持。SMMU 场景下合理使用 ATS 让设备侧缓存翻译结果减少每次 DMA 都走 SMMU 遍历。而 MPU 场景下确定性是天然优势但 region 数量有限要精打细算。我的做法是把最关键的几段栈、任务控制块、外设寄存器用 MPU 保护起来其余内存用默认属性不追求全覆盖。这套东西说到底核心就一句话先想清楚你的地址在哪个层级被翻译、被谁保护再去配对应的单元。MPU、MMU、SMMU 不是三个可以随便替换的选项而是三个位置不同、职责不同的硬件模块。把它们的边界理清楚很多看似玄学的内存问题其实都有明确的排查路径。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑