全部例程

MPU - 内存保护单元

W55MH32 其他标签

2025/02/12 更新

MPU(Memory Protection Unit,内存保护单元)是W55MH32中用于管理内存区域访问权限与属性的关键模块。它通过划分内存区域并设置访问规则(如读/写/执行权限、缓存策略), 增强系统的安全性和稳定性,尤其适用于多任务系统(如RTOS)或需要隔离关键资源的场景。

MPU功能概述

基本概念

MPU是W55MH32内置的硬件模块,不支持虚拟内存(与MMU不同),但能通过物理内存区域划分实现以下功能:

  • 限制任务/程序对特定内存区域的访问(如禁止写、禁止执行)。
  • 定义内存区域的属性(如缓存策略、共享性),优化系统性能。
  • 检测非法内存访问(如越界、权限违规),触发异常(如 MemManage Fault),避免系统崩溃。

关键术语

  • 内存区域(Region) MPU 将内存划分为多个独立区域,每个区域需配置基地址、大小、权限等参数。
  • 访问权限(Access Permission)定义区域的读(R)、写(W)、执行(X)权限(如仅读、可读可写、不可执行)。
  • 内存属性(Memory Attribute) 包括缓存策略(如无缓存、写通、写回)、共享性(是否被多处理器共享)等,影响数据访问效率。

MPU核心功能

内存区域划分

MPU通过配置区域基地址(Base Address)和区域大小(Size),将物理内存划分为多个独立区域。

  • 大小限制:区域大小必须是2的幂次(如32B、64B、1KB、64KB等),且基地址需对齐到区域大小(例如 64KB 区域的基地址必须是 64KB 的整数倍)。
  • 区域重叠:若多个区域重叠,编号大的区域优先级更高(覆盖小编号区域的配置)。

访问权限控制

每个区域可独立设置特权级(Privileged)和用户级(Unprivileged)的访问权限(如 RTOS 中内核运行在特权级,任务运行在用户级)。常见权限组合如下:

权限类型 说明
PRIV_RW 特权级可读可写,用户级无权限
PRIV_RW_USER_RO 特权级可读可写,用户级仅可读
NO_ACCESS 任何级别均不可访问(用于标记非法区域)

内存属性配置

通过设置内存类型(Memory Type)和缓存策略(Cache Policy),优化数据访问效率:

  • 内存类型:如普通内存(Normal)、设备内存(Device)。
    • 普通内存:支持缓存(如SRAM中的变量)。
    • 设备内存:通常为外设寄存器(如 GPIO、UART),需禁用缓存(避免缓存导致的读写延迟)。
  • 缓存策略:
    • 无缓存(Non-Cacheable):直接访问物理内存(如设备寄存器)。
    • TEC > 255时进入离线状态,无法收发报文,需通过自动恢复或软件干预重新上线。

应用场景

MPU的核心价值在于内存安全防护和资源隔离,以下是其典型应用场景,结合核心功能说明其实际意义:

  • RTOS任务隔离(多任务系统核心需求):在RTOS(实时操作系统)中,多个任务共享同一内存空间,若未隔离可能因任务异常(如栈溢出、野指针)导致系统崩溃。MPU通过区域划分与权限控制实现任务隔离。
  • 关键术语/代码保护(防篡改与误操作):系统中某些数据或代码(如固件、校准参数、加密密钥)一旦被修改,可能导致功能失效或安全漏洞。MPU通过只读或禁止访问权限保护这些资源。
  • 外设寄存器访问控制(防止误操作外设)外设寄存器(如GPIO、UART的控制寄存器)的错误修改可能导致外设异常。MPU限制仅特权级代码(如内核)可修改关键寄存器。
  • 内存越界检测(开发调试辅助):外设寄存器(如GPIO、UART的控制寄存器)的错误修改可能导致外设异常。MPU限制仅特权级代码(如内核)可修改关键寄存器。
  • 安全启动与固件保护(系统级安全需求)在需要安全启动的系统中(如医疗设备、工业控制),MPU保护启动代码和安全配置区域,确保系统从可信代码启动。

注意事项

  1. 区域数量限制:W55MH32最多支持8个MPU区域(部分型号可能更少),需合理规划区域(如优先保护关键资源)。
  2. 区域对齐要求:基地址必须对齐到区域大小(如64KB区域的基地址必须是0x20000000、0x20010000等),否则配置无效。
  3. 特权级与用户级区别:内核代码(如RTOS调度器)运行在特权级,可访问所有区域;任务代码运行在用户级,受MPU权限严格限制。
  4. 缓存策略与外设:设备内存(如GPIO、UART寄存器)必须配置为无缓存(MPU_CachePolicy_NoCache),否则缓存会导致读写延迟,引发外设异常。
  5. MPU启用顺序:需先配置所有区域,再启用MPU;若启用后修改区域,需先禁用MPU,修改完成后重新启用。

程序设计

MPU的核心配置通过RASR(Region Attribute and Size Register,区域属性与大小寄存器)实现,具体配置步骤如下:

内存区域大小宏(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)。

内存类型与缓存策略宏(C和S字段)


#define MPU_DEFS_NORMAL_MEMORY_WT    (MPU_RASR_C_Msk | MPU_RASR_S_Msk)

作用:定义普通内存的直写(Write-Through, WT)缓存策略。

原理

  1. C_Msk(位 [16]):使能缓存(Cacheable);
  2. S_Msk(位 [18]):标记为共享内存(Sharable),用于多主设备(如 CPU 与 DMA)访问时的一致性;
  3. 组合后表示 “可缓存、共享的直写内存”(写操作直接更新内存,不经过缓存)。

访问权限宏(AP字段)


#define MPU_DEFS_RASE_AP_FULL_ACCESS (0x3 << MPU_RASR_AP_Pos)

作用:设置内存区域的完全访问权限(无限制)。

原理:RASR的AP字段(位 [23:21])定义访问权限,0x3表示:

  • 特权模式(Privileged)允许读/写;
  • 用户模式(User)允许读/写(无限制访问)。

设置内存保护规则

内存保护规则由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及串口外设的访问受限于预设的大小、权限和缓存策略,提升系统内存访问的安全性与稳定性。

主函数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对内存访问权限的限制效果;最后进入无限循环保持运行。

下载验证

程序下载运行之后,首先打印了时钟信息,接着是MPU的相关寄存器初始状态信息,然后用特权限制访问内存成功,之后设置为用户访问,再次访问则无法读取内容:

总结

W55MH32的MPU通过灵活的内存区域划分和权限控制,为嵌入式系统提供了内存安全防护和性能优化的能力。其核心价值在于:

  • 隔离性:防止任务/程序间的非法内存访问,提升系统可靠性。
  • 安全性:保护关键数据(如固件、中断向量表)不被篡改。
  • 调试便利性:通过检测内存越界,加速开发阶段的bug定位。

实际应用中,需结合具体场景(如RTOS任务隔离、关键数据保护)配置MPU区域,并注意区域对齐、权限设置和缓存策略的合理性。

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板