子区域重叠设置
2026/10/3大约 4 分钟
- 背景区域(Background Region):MPU 没有画圈保护的地方(三不管地带)。
- 缺省(Default):你对 MPU 没交代时,它默认的态度(由
PRIVDEFENA开关控制:0 就是全封杀,1 就是特权放行,非特权封杀)。 - 子区域(Subregion):把一个大的保护圈,切成 8 块小蛋糕。哪块坏了,可以单独把哪块踢出保护圈。
- 重叠(Overlap):两个圈在物理地址上撞车了。此时听小圈(优先级高/权限严)的,大圈靠边站
#include <stdio.h>
#include "ARMCM3.h"
// 进入非特权模式(User Mode)
// 注意:这个函数在这里定义了但没被调用!所以代码全程跑在特权模式(Privileged Mode)下。
// 如果调用了它,代码执行到最后那行“测试写入”时,会因为非特权权限不够而直接触发 MemManage Fault。
void switch_user (void) {
__set_CONTROL(CONTROL_nPRIV_Msk);
}
// 宏定义:直接操作指定地址的内存
#define MEM32(addr) *(volatile unsigned int *)(addr)
int main (void) {
// 1. 读取当前芯片的 MPU 到底支持几个区域(通过 MPU->TYPE 寄存器)
int dregion = (MPU->TYPE & MPU_TYPE_DREGION_Msk) >> MPU_TYPE_DREGION_Pos;
printf("dregion count: %d\n", dregion);
// 2. 【伏笔】趁现在还没开启 MPU,先把数据写进 0x20010000。
// 等会配好 MPU 后,这个地址会被划分权限,这行就是留个底,等会测试内存有没有被保护。
MEM32(0x20010000) = 0x12345678;
#if 1
// ================= 【区域0:大圈】 =================
// 基地址:0x20000000,区域大小:256KB
// 权限:ARM_MPU_AP_PRIV(特权程序只读!非特权直接死)
ARM_MPU_SetRegion(
ARM_MPU_RBAR(0, 0x20000000), // 注意这个地址要对齐
ARM_MPU_RASR(
1, // DisableExec:是否允许取指(1=不允许,但这是一块内存,取指个毛线)
ARM_MPU_AP_PRIV, // AccessPermission:特权程序只读
0, // TypeExtField(不用管,默认0)
0, // IsShareable(不共享)
0, // IsCacheable(不缓存)
0, // IsBufferable(不缓冲)
// 【核心操作】SubRegionDisable!
// 256KB等分成8块,每块32KB(编号0~7)。
// (1<<2)|(1<<3) 就是把第2块和第3块(总共64KB)踢出大圈的管辖范围。
// 踢出去的意思是“我不管这块地了,你们别人去管”,也就是在大圈里“挖了个洞”!
// 洞的地址范围正好是 0x20010000 ~ 0x2001FFFF。
(1 << 2) | (1 << 3),
ARM_MPU_REGION_SIZE_256KB // 区域大小为256KB
)
);
// ================= 【区域1:小圈】 =================
// 基地址:0x20010000(正好精准地盖住区域0挖出的那个洞!)
// 大小:64KB
// 权限:ARM_MPU_AP_PRO(特权程序可读写!注意,这里权限比大圈宽!)
ARM_MPU_SetRegion(
ARM_MPU_RBAR(1, 0x20000000+64*1024), // 注意这个地址要对齐,加64KB正好是0x20010000
ARM_MPU_RASR(
1, // DisableExec
ARM_MPU_AP_PRO, // AccessPermission:特权程序可读写!
0, 0, 0, 0, // 其他属性全默认
0, // SubRegionDisable=0:小圈不挖洞,把自己这64KB管得死死的
ARM_MPU_REGION_SIZE_64KB // 区域大小:64KB
)
);
// ================= 【区域2:代码区】 =================
// 基地址:0x0,大小256KB,覆盖Flash的向量表和代码
ARM_MPU_SetRegion(
ARM_MPU_RBAR(2, 0), // 注意这个地址要对齐
ARM_MPU_RASR(
0, // DisableExec:0,允许取指!否则CPU一上电过来取指就死机!
ARM_MPU_AP_PRO, // 特权程序只读
0, 0, 0, 0,
0,
ARM_MPU_REGION_SIZE_256KB
)
);
// ================= 【开启MPU(最狠的一步)】 =================
// 参数为 0,意味着 MPU_CTRL_PRIVDEFENA_Msk 位没置1!
// 也就是“关闭缺省背景区”。
// 现在的局面是:不管是特权机还是非特权机,只要访问没被上面三个圈覆盖的地方,
// 统统触发 MemManage Fault!这就是“铁面无私”的沙箱模式。
ARM_MPU_Enable(0);
#else
// ================= 【备用方案:利用“缺省”走后门】 =================
// 只画一个小圈:0x20010000 开始的 32KB(特权可读写)
ARM_MPU_SetRegion(
ARM_MPU_RBAR(0, 0x20000000 + 64*1024),
ARM_MPU_RASR(
1, // DisableExec
ARM_MPU_AP_PRO, // 特权程序只读
0, 0, 0, 0,
0,
ARM_MPU_REGION_SIZE_32KB
)
);
// 开启 MPU,并且带上 MPU_CTRL_PRIVDEFENA_Msk
// 意思是:特权机(内核)可以访问没画圈的地方(背景区域);
// 非特权机(任务)依然只能访问这一个 32KB 圈,碰其他地方就死。
ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);
#endif
printf("dregion count: %d\n", dregion);
// ================= 【实测写入】 =================
// 在 #if 1 分支下,这一行写入会成功!
// 因为 0x20010000 正好是区域1(64KB 特权读写)的势力范围,当前又在特权模式下,所以可以直接写。
// (如果调用了 switch_user() 进入非特权模式,再去写这块内存,直接触发 MemManage Fault!)
MEM32(0x20000000 + 64 * 1024) = 0x87654321;
for (;;) {
}
return 0;
}
