MPU通过配置栈区region(基址对齐、大小为2的幂、AP设为特权读写/用户无访问)实现任务栈保护;FreeRTOS需启用MPU wrappers并配合vPortStoreTaskMPUSettings动态加载各任务region;越界时查SCB->MMFAR定位故障地址。

MPU 在 Cortex-M3/M4/M7 上怎么配 region 才能保护任务栈
MPU 本身不直接“隔离任务”,它靠配置内存区域(region)的基址、大小、访问权限来实现保护。关键不是“给每个任务配一个 region”,而是把栈所在的内存块设为不可执行、只允许当前任务访问——通常配合 FreeRTOS 的 vPortStoreTaskMPUSettings 或裸机中手动配置 MPU_RBAR/MPU_RASR 寄存器。
常见错误是 region 大小没对齐:Cortex-M 的 MPU 要求 region size 必须是 2n 字节(32B–4GB),且起始地址必须按 size 对齐。比如栈区 2KB,就得配成 2048B(211),起始地址必须是 2048 的整数倍;若栈从 0x20001234 开始,就不能直接用 2KB region,得向上对齐到 0x20001000 或向下预留空间。
- 用
MPU->RBAR = (address & ~0x3F) | MPU_RBAR_VALID | region_idx写基址(低 6 位强制清零) -
MPU->RASR中的SIZE字段填的是 “log2(size)-1”,例如 2KB 填10(因为 211=2048) - 栈 region 的
AP(access permission)建议设为 “privileged read-write, unprivileged no-access”(值0b101),防止用户态任务误写其他栈
FreeRTOS 下怎么让每个任务自动获得独立 MPU region
FreeRTOS 官方 Cortex-M port(如 ARM_CM33_NTZ)默认不启用 per-task MPU——它只在启动时配一个全局 region 保内核栈。要支持多任务栈隔离,必须启用 configUSE_MPU_WRAPPERS_V1(旧版)或升级到 v11+ 并启用 configENABLE_MPU + configUSE_TASK_PREEMPTION_DISABLE 等配套开关。
核心机制是:创建任务时,FreeRTOS 调用 vPortStoreTaskMPUSettings 把该任务栈地址、大小、权限写入任务 TCB 的 xMPUSettings 字段;任务切换时,PendSV 异常里调用 vMPURestoreContext 把对应 region 加载进 MPU。
立即学习“C++免费学习笔记(深入)”;
- 必须确保每个任务栈单独分配(不能共用 heap_4 的大块内存),推荐用
xTaskCreateRestricted或xTaskCreateRestrictedStatic - TCB 结构体需包含
xMPUSettings,因此编译时要定义portUSING_MPU_WRAPPERS - 中断服务程序(ISR)不能用任务栈,所有 ISR 栈必须放在单独的、MPU 允许访问的 region(如 core SRAM 区)里
栈越界触发 MPU fault 后怎么定位具体哪个任务踩了谁
MPU fault 不会自动告诉你“任务 A 越界写了任务 B 的栈”,它只产生 MemManage_IRQn,你需要在 handler 里查 SCB->MMFAR(fault address)和 SCB->MMFSR(fault status),再人工比对各任务栈范围。
实际调试时最有效的办法是:在 MemManage_Handler 里立即 halt,用 debugger 查 SCB->MMFAR,然后遍历所有任务的 pxTopOfStack 和栈大小,看落在哪个区间;或者加日志:每次任务切换前把当前栈顶地址和 size 打印出来,故障时对比 MMFAR 就能锁定。
SCB->MMFSR & (1 为真表示是 MPU fault(不是 MMU)SCB->MMFSR & (1 表示是数据访问 fault(大概率是栈写越界)- 别依赖
__get_PSP()获取 fault 时的栈指针——MPU fault 可能发生在 handler mode,此时用的是 MSP
为什么开了 MPU 还是拦不住栈溢出到相邻变量
MPU 只能防护“跨 region 访问”,如果两个变量(比如栈和全局数组)都在同一个 region 里,哪怕栈溢出盖掉了那个数组,MPU 也完全无感。真正防这种问题得靠编译器插桩(-fstack-protector)或运行时栈哨兵(如 FreeRTOS 的 configCHECK_FOR_STACK_OVERFLOW=2)。
另一个常见疏漏是:MPU region 没覆盖整个栈区。例如任务栈申请了 1024 字节,但 region 只配了 512 字节,剩下一半裸奔。更糟的是 region size 配大了(比如配成 4KB),结果把后面的任务栈或堆也包进来了,反而失去隔离意义。
- region 起始地址必须 ≤ 栈底地址,结束地址必须 ≥ 栈顶地址(即
stack_start到stack_start + stack_size) - 务必确认 linker script 里栈区没有和其他 section(如
.bss)紧邻且无 gap,否则越界可能静默落到合法内存上 - MPU 不检查栈增长方向——Cortex-M 默认满递减(Full Descending),栈顶在低地址,所以 region 必须从栈底(高地址)开始向下覆盖整个范围
MPU 栈保护本质是“划边界”,不是“贴膏药”。边界的粒度、对齐、权限组合稍有偏差,就既挡不住越界,又拖慢上下文切换。真要防住栈问题,得把 MPU、编译器选项、RTOS 检查、静态分析几层叠起来用,单靠寄存器配置解决不了全部。


















