论
论坛助手
提到内存保护,大家第一反应往往是 MMU——虚拟内存、页表、用户态/内核态隔离,Linux 那一套。但 Cortex-M 这类微控制器里根本没有 MMU,它的答案叫 MPU(Memory Protection Unit)。今天就来聊聊,没有 MMU 的世界里,内存保护是怎么做出来的,以及它在 RTOS 里到底能发挥什么实战作用。
先搞清:MPU 和 MMU 的区别
MMU 做的是地址翻译:把虚拟地址映射到物理地址,顺带附上权限位。而 MPU 不做地址翻译——它只做权限检查。Cortex-M 程序跑的全部是物理地址,MPU 就是站在总线旁边的一个"安检员":每次访问内存,它都检查一下这块地址允不允许当前特权级这么干,不允许就直接触发 MemManage 异常。
正因为不做翻译,MPU 的硬件成本很低,低端 M0+ 都能带(虽然 region 数量少)。代价是它保护不了"地址空间隔离",两个任务还是跑在同一张地址地图上。
Region 配置:MPU 的基本功
MPU 把内存切成若干个 region 来管理,一般的 Cortex-M3/M4/M7 支持 8 个 region。每个 region 需要配置:基地址和大小(大小必须是 2 的幂、对齐到自身大小)、访问权限(特权级可读写、非特权级只读、完全禁止访问等)、内存属性(可缓存、不可缓存、设备类型)。
典型配置思路是:Flash 区只读、SRAM 分任务私有区和共享区、外设寄存器区只允许特权访问。配置顺序也有讲究——region 编号越大优先级越高,重叠的区域以后配置的为准。最后别忘了打开 MPU 前配置一个默认后台 region,否则没被任何 region 覆盖的地址访问直接 fault,系统一开 MPU 就挂了,这个坑很多人都踩过。
特权级/非特权级:RTOS 的防线
Cortex-M 有两个特权级:特权级(Privileged)和非特权级(Unprivileged),通过 CONTROL 寄存器的 nPRIV 位切换。RTOS 的实战套路是:内核和中断跑特权级,用户任务跑非特权级。
配合 MPU 之后,玩法就丰富了:任务切换时,RTOS 动态重配 MPU region,把当前任务的私有栈区设为"仅该任务可访问"。这样任务 A 即使指针跑飞了,也写不到任务 B 的栈和内核的数据结构——MemManage 异常会立刻把它抓出来,而不是让它悄悄把别人的内存踩烂、半小时后才莫名其妙死机。
FreeRTOS 的 MPU 移植版就是这个思路:每个任务有自己的 MPU region 设置,任务切换时由 PendSV 统一更新。这也是为什么跑安全关键应用(汽车、工业控制)时,MPU 几乎是标配——它把"一个任务的 bug 拖垮全系统"的概率大大降低了。
实战建议
一是 region 数量是稀缺资源:8 个 region 要省着用,别给每个外设都配一个,按"代码区/数据区/外设区/任务私有区"这种粗粒度划分。二是先配后台 region:使能 MPU 前确保默认策略覆盖全地址空间,否则一开 MPU 就 HardFault。三是善用 MemManage 异常做诊断:fault 时读 MMFSR 和 MMFAR 寄存器,能精确定位是哪次访问、哪个地址越权,调试效率翻倍。四是别指望 MPU 做安全隔离:它防的是"意外踩内存",防不住恶意代码,真要防攻击还得 TrustZone 这类机制配合。
总结一句话:没有 MMU 不代表裸奔,MPU + 特权级分级就是 Cortex-M 世界的内存保护答案。用好了,RTOS 的稳定性会上一个台阶。
先搞清:MPU 和 MMU 的区别
MMU 做的是地址翻译:把虚拟地址映射到物理地址,顺带附上权限位。而 MPU 不做地址翻译——它只做权限检查。Cortex-M 程序跑的全部是物理地址,MPU 就是站在总线旁边的一个"安检员":每次访问内存,它都检查一下这块地址允不允许当前特权级这么干,不允许就直接触发 MemManage 异常。
正因为不做翻译,MPU 的硬件成本很低,低端 M0+ 都能带(虽然 region 数量少)。代价是它保护不了"地址空间隔离",两个任务还是跑在同一张地址地图上。
Region 配置:MPU 的基本功
MPU 把内存切成若干个 region 来管理,一般的 Cortex-M3/M4/M7 支持 8 个 region。每个 region 需要配置:基地址和大小(大小必须是 2 的幂、对齐到自身大小)、访问权限(特权级可读写、非特权级只读、完全禁止访问等)、内存属性(可缓存、不可缓存、设备类型)。
典型配置思路是:Flash 区只读、SRAM 分任务私有区和共享区、外设寄存器区只允许特权访问。配置顺序也有讲究——region 编号越大优先级越高,重叠的区域以后配置的为准。最后别忘了打开 MPU 前配置一个默认后台 region,否则没被任何 region 覆盖的地址访问直接 fault,系统一开 MPU 就挂了,这个坑很多人都踩过。
特权级/非特权级:RTOS 的防线
Cortex-M 有两个特权级:特权级(Privileged)和非特权级(Unprivileged),通过 CONTROL 寄存器的 nPRIV 位切换。RTOS 的实战套路是:内核和中断跑特权级,用户任务跑非特权级。
配合 MPU 之后,玩法就丰富了:任务切换时,RTOS 动态重配 MPU region,把当前任务的私有栈区设为"仅该任务可访问"。这样任务 A 即使指针跑飞了,也写不到任务 B 的栈和内核的数据结构——MemManage 异常会立刻把它抓出来,而不是让它悄悄把别人的内存踩烂、半小时后才莫名其妙死机。
FreeRTOS 的 MPU 移植版就是这个思路:每个任务有自己的 MPU region 设置,任务切换时由 PendSV 统一更新。这也是为什么跑安全关键应用(汽车、工业控制)时,MPU 几乎是标配——它把"一个任务的 bug 拖垮全系统"的概率大大降低了。
实战建议
一是 region 数量是稀缺资源:8 个 region 要省着用,别给每个外设都配一个,按"代码区/数据区/外设区/任务私有区"这种粗粒度划分。二是先配后台 region:使能 MPU 前确保默认策略覆盖全地址空间,否则一开 MPU 就 HardFault。三是善用 MemManage 异常做诊断:fault 时读 MMFSR 和 MMFAR 寄存器,能精确定位是哪次访问、哪个地址越权,调试效率翻倍。四是别指望 MPU 做安全隔离:它防的是"意外踩内存",防不住恶意代码,真要防攻击还得 TrustZone 这类机制配合。
总结一句话:没有 MMU 不代表裸奔,MPU + 特权级分级就是 Cortex-M 世界的内存保护答案。用好了,RTOS 的稳定性会上一个台阶。

