第三十二章 MPU——内存保护单元
目录
MPU(Memory Protection Unit,内存保护单元)是W55MH32中用于管理内存区域访问权限与属性的关键模块。它通过划分内存区域并设置访问规则(如读/写/执行权限、缓存策略),增强系统的安全性和稳定性,尤其适用于多任务系统(如RTOS)或需要隔离关键资源的场景。
1 MPU功能概述
1.1 基本概念
MPU是W55MH32内置的硬件模块,不支持虚拟内存(与MMU不同),但能通过物理内存区域划分实现以下功能:
- 限制任务/程序对特定内存区域的访问(如禁止写、禁止执行)。
- 定义内存区域的属性(如缓存策略、共享性),优化系统性能。
- 检测非法内存访问(如越界、权限违规),触发异常(如 MemManage Fault),避免系统崩溃。
1.2 关键术语
- 内存区域(Region):MPU 将内存划分为多个独立区域,每个区域需配置基地址、大小、权限等参数。
- 访问权限(Access Permission):定义区域的读(R)、写(W)、执行(X)权限(如仅读、可读可写、不可执行)。
- 内存属性(Memory Attribute):包括缓存策略(如无缓存、写通、写回)、共享性(是否被多处理器共享)等,影响数据访问效率。
2 MPU核心功能
2.1 内存区域划分
MPU通过配置区域基地址(Base Address)和区域大小(Size),将物理内存划分为多个独立区域。
- 大小限制:区域大小必须是2的幂次(如32B、64B、1KB、64KB等),且基地址需对齐到区域大小(例如 64KB 区域的基地址必须是 64KB 的整数倍)。
- 区域重叠:若多个区域重叠,编号大的区域优先级更高(覆盖小编号区域的配置)。
2.2 访问权限控制
每个区域可独立设置特权级(Privileged)和用户级(Unprivileged)的访问权限(如 RTOS 中内核运行在特权级,任务运行在用户级)。常见权限组合如下:
权限类型 | 说明 |
PRIV_RW | 特权级可读可写,用户级无权限 |
PRIV_RW_USER_RO | 特权级可读可写,用户级仅可读 |
PRIV_RO | 特权级仅可读,用户级无权限 |
NO_ACCESS | 任何级别均不可访问(用于标记非法区域) |
2.3 内存属性配置
通过设置内存类型(Memory Type)和缓存策略(Cache Policy),优化数据访问效率:
- 内存类型:如普通内存(Normal)、设备内存(Device)。
- 普通内存:支持缓存(如SRAM中的变量)。
- 设备内存:通常为外设寄存器(如 GPIO、UART),需禁用缓存(避免缓存导致的读写延迟)。
- 缓存策略:
- 无缓存(Non-Cacheable):直接访问物理内存(如设备寄存器)。
- 写通(Write-Through):写数据时同时更新缓存和内存(适合需要实时性的场景)。
- 写回(Write-Back):写数据时仅更新缓存,后续统一写入内存(适合高频读写场景,提升效率)。
3 应用场景
MPU的核心价值在于内存安全防护和资源隔离,以下是其典型应用场景,结合核心功能说明其实际意义:
- RTOS任务隔离(多任务系统核心需求):在RTOS(实时操作系统)中,多个任务共享同一内存空间,若未隔离可能因任务异常(如栈溢出、野指针)导致系统崩溃。MPU通过区域划分与权限控制实现任务隔离。
- 关键数据/代码保护(防篡改与误操作):系统中某些数据或代码(如固件、校准参数、加密密钥)一旦被修改,可能导致功能失效或安全漏洞。MPU通过只读或禁止访问权限保护这些资源。
- 外设寄存器访问控制(防止误操作外设):外设寄存器(如GPIO、UART的控制寄存器)的错误修改可能导致外设异常。MPU限制仅特权级代码(如内核)可修改关键寄存器。
- 内存越界检测(开发调试辅助):开发阶段,程序可能因数组越界、野指针等错误访问未分配内存。MPU将未使用的内存区域配置为NO_ACCESS(无访问权限),触发异常以快速定位问题。
- 安全启动与固件保护(系统级安全需求):在需要安全启动的系统中(如医疗设备、工业控制),MPU保护启动代码和安全配置区域,确保系统从可信代码启动。
4 注意事项
引脚驱动能力:MCO输出频率不宜过高(需低于GPIO的最大可靠频率,通常建议不超过50MHz),高频时需考虑信号完整性(如阻抗匹配)。
时钟源使能顺序:配置MCO前需确保时钟源已稳定(如HSE起振完成),避免输出无效信号。
5 程序设计
MPU的核心配置通过RASR(Region Attribute and Size Register,区域属性与大小寄存器)实现,具体配置步骤如下:
5.1 内存区域大小宏(SIZE字段)
#define MPU_DEFS_RASR_SIZE_1KB (0x09 << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_16KB (0x0D << MPU_RASR_SIZE_Pos)
#define MPU_DEFS_RASR_SIZE_64KB (0x0F << MPU_RASR_SIZE_Pos)
作用:设置内存区域的大小。
原理:RASR的SIZE字段(位 [5:0])用于定义区域大小,实际大小为2(SIZE+1)字节。
- 0x09对应SIZE=9,计算得210=1024字节(1KB);
- 0x0D对应SIZE=13,计算得214=16384字节(16KB);
- 0x0F对应SIZE=15,计算得216=65536字节(64KB)。
5.2 内存类型与缓存策略宏(C和S字段)
#define MPU_DEFS_NORMAL_MEMORY_WT (MPU_RASR_C_Msk | MPU_RASR_S_Msk)
作用:定义普通内存的直写(Write-Through, WT)缓存策略。
原理:
- C_Msk(位 [16]):使能缓存(Cacheable);
- S_Msk(位 [18]):标记为共享内存(Sharable),用于多主设备(如 CPU 与 DMA)访问时的一致性;
- 组合后表示 “可缓存、共享的直写内存”(写操作直接更新内存,不经过缓存)。
5.3 访问权限宏(AP字段)
#define MPU_DEFS_RASE_AP_FULL_ACCESS (0x3 << MPU_RASR_AP_Pos)
作用:设置内存区域的完全访问权限(无限制)。
原理:RASR的AP字段(位 [23:21])定义访问权限,0x3表示:
- 特权模式(Privileged)允许读/写;
- 用户模式(User)允许读/写(无限制访问)。
5.4 设置内存保护规则
内存保护规则由MPU_Set()函数实现,函数内容如下:
{
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
mpu_disable();
mpu_region_config(0, 0x8000000, MPU_DEFS_RASR_SIZE_64KB,
MPU_DEFS_NORMAL_MEMORY_WT | MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk);
mpu_region_config(1, 0x20000000, MPU_DEFS_RASR_SIZE_16KB,
MPU_DEFS_NORMAL_MEMORY_WT | MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk);
mpu_region_config(2, USART1_BASE, MPU_DEFS_RASR_SIZE_1KB,
MPU_DEFS_NORMAL_MEMORY_WT | MPU_DEFS_RASE_AP_FULL_ACCESS | MPU_RASR_ENABLE_Msk);
mpu_region_disable(3);
mpu_region_disable(4);
mpu_region_disable(5);
mpu_region_disable(6);
mpu_region_disable(7);
mpu_enable();
}
该函数首先使能了内存错误(MemFault)异常,以便检测非法内存访问;随后禁用MPU(配置前需禁用以避免冲突),依次配置3个内存区域:
- 区域0(起始地址0x8000000,64KB,对应Flash)
- 区域1(起始地址0x20000000,16KB,对应RAM)
- 区域2(起始地址USART1_BASE,1KB,对应串口1外设寄存器)
这3个区域均设置为“直写缓存+完全访问权限”并启用;接着禁用未使用的区域3~7(MPU通常支持8个区域,未使用的需禁用以防意外访问);最后启用MPU,使所有配置的内存保护规则生效,确保Flash、RAM及串口外设的访问受限于预设的大小、权限和缓存策略,提升系统内存访问的安全性与稳定性。
5.5 主函数main()
主函数main()的内容如下:
int main(void)
{
RCC_ClocksTypeDef clocks;
delay_init();
UART_Configuration(115200);
RCC_GetClocksFreq(&clocks);
printf("\n");
printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
(float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
(float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);
printf("MPU Test.\n");
printf("MPU->TYPE, Value: 0x%x\n", MPU->TYPE);
printf("MPU->CTRL, Value: 0x%x\n", MPU->CTRL);
printf("MPU->RNR, Value: 0x%x\n", MPU->RNR);
printf("MPU->RBAR, Value: 0x%x\n", MPU->RBAR);
printf("MPU->RASR, Value: 0x%x\n", MPU->RASR);
printf("MPU->RBAR_A1;, Value: 0x%x\n", MPU->RBAR_A1);
printf("MPU->RASR_A1, Value: 0x%x\n", MPU->RASR_A1);
printf("MPU->RBAR_A2, Value: 0x%x\n", MPU->RBAR_A2);
printf("MPU->RASR_A2, Value: 0x%x\n", MPU->RASR_A2);
printf("MPU->RBAR_A3, Value: 0x%x\n", MPU->RBAR_A3);
printf("MPU->RASR_A3, Value: 0x%x\n\n", MPU->RASR_A3);
printf("LimiteToPrivilege Access\n");
SHOW_PrintFlash(0x08000000, 64);
MPU_Set();
printf("LimiteToUser Access\n");
SHOW_PrintFlash(0x08000000, 64);
while (1);
}
main()函数是MPU功能测试的主程序:首先初始化延时函数和串口(波特率115200),调用标准库获取系统各时钟域频率(SYSCLK、HCLK等)并打印;接着打印MPU核心寄存器(TYPE、CTRL等)的初始状态用于调试;
随后调用MPU_Set配置内存保护规则,对比配置前后对Flash起始地址(0x08000000)前64字节的访问结果(特权模式与用户模式),验证MPU对内存访问权限的限制效果;最后进入无限循环保持运行。
6 下载验证
程序下载运行之后,首先打印了时钟信息,接着是MPU的相关寄存器初始状态信息,然后用特权限制访问内存成功,之后设置为用户访问,再次访问则无法读取内容:
7 总结
MCO功能通过灵活配置时钟源和分频系数,为W55MH32提供了对外输出时钟的能力,简化了系统设计中的时钟同步问题。先理解其应用场景(如多芯片同步、调试测量),再掌握配置步骤(时钟源选择、GPIO设置、分频配置),可更高效地应用于实际项目中。