4.STM32H743内存保护单元(MPU)
STM32H743的MPU(Memory Protection Unit,内存保护单元)是内核级的硬件安全机制,核心作用是划分内存区域限制访问权限(比如只读/只写/可执行)、校验访问主体(CPU特权/非特权模式),防止程序错误(如数组越界、野指针)或恶意代码破坏关键内存(如内核数据、外设寄存器),是提升系统稳定性和安全性的核心组件。代码量较大/多任务,担心内存错误导致崩溃;有安全/稳定要求(商用、
一、介绍MPU
STM32H743的MPU(Memory Protection Unit,内存保护单元)是内核级的硬件安全机制,核心作用是划分内存区域、限制访问权限(比如只读/只写/可执行)、校验访问主体(CPU特权/非特权模式),防止程序错误(如数组越界、野指针)或恶意代码破坏关键内存(如内核数据、外设寄存器),是提升系统稳定性和安全性的核心组件。
内核外设,所以在STM32的参考手册中没有相关介绍。需要去看ARM内核手册。
一、MPU的核心基础
1. MPU的归属与核心能力
STM32H743基于ARM Cortex-M7内核,MPU是Cortex-M7内核的标配模块主要能力:
划分最多8个主区域(Region) + 支持子区域(Subregion)细分(每个主区域可拆分为8个子区域);
对每个区域设置访问权限(特权/非特权、读/写/执行);
检测非法访问(如非特权代码写内核数据、执行RAM中的数据),触发硬件故障中断(HardFault);
支持不同内存类型(Normal/Device/Strongly Ordered)的属性配置(匹配总线访问特性)。
2. 关键概念
|
术语 |
通俗解释 |
|
特权模式(Privileged) |
内核的“管理员模式”,能访问所有内存/外设,通常是操作系统内核、中断服务程序运行的模式 |
|
非特权模式(Unprivileged) |
内核的“普通用户模式”,只能访问MPU允许的内存,通常是应用任务运行的模式 |
|
主区域(Region) |
MPU划分的最小独立内存块,最小粒度32字节,且地址必须是“区域大小的整数倍” |
|
子区域(Subregion) |
把1个主区域拆成8个相等的小块,可单独禁用某几个子区域(灵活适配不规则内存) |
|
内存类型 |
Normal(普通内存,如SRAM)、Device(外设寄存器)、Strongly Ordered(严格有序) |
|
XN(Execute Never) |
“禁止执行”,标记该区域不能作为代码运行(防止RAM中注入恶意代码执行) |
二、STM32H743的内存布局
H743的核心内存区域,MPU保护的对象就是这些区域:
|
内存类型 |
地址范围(核心) |
用途 |
保护需求示例 |
|
ITCM RAM |
0x00000000 - 0x0007FFFF |
指令缓存RAM(可执行) |
禁止非特权写、允许执行 |
|
DTCM RAM |
0x20000000 - 0x2007FFFF |
数据缓存RAM |
禁止执行(XN)、特权可写 |
|
AXI SRAM |
0x24000000 - 0x240FFFFF |
高速数据RAM |
非特权只读、禁止执行 |
|
SRAM1/2/3 |
0x30000000 - 0x300FFFFF |
通用数据RAM |
按任务划分访问权限 |
|
外设寄存器 |
0x40000000 - 0x5FFFFFFF |
外设控制/状态寄存器 |
仅特权可写、禁止执行 |
|
Flash |
0x08000000 - 0x08FFFFFF |
程序存储 |
禁止非特权写、允许执行 |
三、MPU的核心配置步骤
MPU配置的核心是“定义区域→设置权限→使能MPU”,以下是基于HAL库的极简示例(核心逻辑):
步骤1:禁用MPU(配置前必须)
MPU_Disable(); // 内核函数,禁用MPU
步骤2:配置一个内存区域(示例:保护AXI SRAM)
MPU_Region_InitTypeDef MPU_InitStruct = {0};
// 1. 配置区域基本信息
MPU_InitStruct.Enable = MPU_REGION_ENABLE; // 使能该区域
MPU_InitStruct.Number = MPU_REGION_NUMBER0; // 选择第0个主区域
MPU_InitStruct.BaseAddress = 0x24000000; // 区域起始地址(AXI SRAM起始)
MPU_InitStruct.Size = MPU_REGION_SIZE_1MB; // 区域大小(1MB,匹配AXI SRAM)
MPU_InitStruct.SubRegionDisable = 0x00; // 不禁用任何子区域(8个子区域都启用)
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; // 内存类型扩展字段(Normal内存)
// 2. 配置访问权限
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_UNPRIV_RO; // 特权可读写、非特权只读
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; // 禁止执行(XN=1)
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; // 非共享(SRAM独占)
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; // 可缓存(提升性能)
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; // 可缓冲
// 3. 应用配置
HAL_MPU_ConfigRegion(&MPU_InitStruct);
步骤3:使能MPU(配置完成后)
// 使能MPU,同时允许特权模式访问默认区域(未配置的区域)
MPU_Enable(MPU_PRIVILEGED_DEFAULT);
四、避坑点
1. 区域地址/大小必须对齐
起始地址必须是“区域大小的整数倍”(比如配置1MB区域,地址必须是0x24000000、0x24100000等,不能是0x24000001);
区域大小只能选内核支持的固定值(32B、64B、128B、256B、512B、1KB、2KB…最大4GB),不能自定义。
2. 禁用MPU后,所有保护失效
调试阶段如果触发HardFault,可先禁用MPU排查是否是MPU权限配置错误;
产品发布前必须确保MPU使能,且权限配置严谨。
3. 中断服务程序(ISR)运行在特权模式
ISR默认是特权模式,所以MPU对ISR的限制仅看“特权权限”,需确保ISR访问的内存有特权权限;
如果ISR需要写某块RAM,该区域的特权权限必须设为RW(读写)。
4. XN位(禁止执行)必须开启数据区
所有数据RAM(DTCM、AXI SRAM、SRAM1/2/3)都要设置DisableExec = 1,防止黑客将代码注入RAM并执行;
Flash区域通常开启执行(DisableExec=0),但要禁止非特权写。
5. 触发HardFault?先查MPU
如果程序跑飞触发HardFault,优先检查:
是否访问了MPU禁止的区域(如非特权写只读区域);
区域地址/大小是否对齐;
权限配置是否与访问行为冲突(如执行RAM中的数据)。
五、使用建议
简单场景:
配置“保护Flash禁止非特权写”+“保护RAM禁止执行”,这是最基础且必要的配置;
用调试器(如ST-Link)查看HardFault的栈信息,定位MPU违规的具体地址。
结合RTOS使用:
如果用FreeRTOS等RTOS,可给不同任务分配非特权模式,通过MPU隔离任务内存,防止一个任务崩溃影响整个系统;
RTOS的内核部分运行在特权模式,仅开放必要的内存区域给非特权任务。
二、什么场景下使用MPU
STM32H743的MPU(内存保护单元)不是“所有场景都必须用”,而是针对需要提升系统稳定性、安全性、可靠性的场景 设计的硬件机制。
一、核心场景1:防止程序错误导致的系统崩溃(最常用)
适用场景
程序中有数组越界、野指针、栈溢出等常见bug;
复杂项目(如多任务、大代码量)中,难以通过纯软件排查所有内存错误;
产品要求“不能因为局部代码错误导致整机死机”(如工业控制、智能家居)。
具体例子
- 保护关键数据区:比如把存储系统参数的SRAM区域配置为“仅特权模式可写”,非特权的应用代码即使出错,也无法篡改关键参数(如校准值、设备ID);
- 禁止RAM执行代码:所有数据RAM(DTCM、AXI SRAM)开启
XN(禁止执行),即使野指针把数据覆盖成指令,CPU也无法执行,避免程序跑飞成随机指令; - 限制栈大小:给任务栈分配独立的MPU区域,一旦栈溢出访问到区域外内存,立即触发HardFault,提前定位问题(而非系统静默崩溃)。
配置思路
// 示例:保护系统参数区(0x30000000-0x30000FFF,4KB)
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_4KB;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_UNPRIV_RO; // 非特权只读
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
二、核心场景2:隔离特权/非特权代码(适配RTOS/多任务)
适用场景
使用FreeRTOS、RT-Thread等RTOS,需要“任务隔离”(一个任务崩溃不影响其他任务/内核);
系统分为“内核层(特权)”和“应用层(非特权)”,需限制应用层的访问范围;
第三方代码/用户自定义代码运行在非特权模式,防止其破坏系统内核。
具体例子
- RTOS任务隔离:给每个应用任务分配独立的MPU区域(如任务栈、任务私有数据),仅开放该任务需要的内存/外设,即使任务出错,也只能触发自身的HardFault,内核和其他任务仍能正常运行;
- 内核保护:把RTOS内核代码/数据所在的Flash/SRAM配置为“仅特权模式访问”,非特权任务无法修改内核的TCB(任务控制块)、调度器等关键数据;
- 外设隔离:比如让“电机控制任务”仅能访问TIM/PWM外设寄存器,“通信任务”仅能访问UART/SPI外设寄存器,防止越权操作外设。
配置思路
// 示例:非特权任务仅能访问UART4外设寄存器(0x40004C00-0x40004FFF,16KB)
MPU_InitStruct.BaseAddress = 0x40004C00;
MPU_InitStruct.Size = MPU_REGION_SIZE_16KB;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_UNPRIV_RW; // 非特权可读写
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; // Device内存(外设寄存器)
三、核心场景3:提升系统安全性(防篡改/防注入)
适用场景
产品面向商用/工业场景,需要防止恶意代码注入、关键代码/数据被篡改;
符合功能安全要求(如IEC 61508),需通过硬件机制降低故障风险;
存储敏感数据(如密钥、密码)的内存区域需要严格保护。
具体例子
- Flash写保护:把存储程序的Flash区域配置为“仅特权模式可写(烧录/升级时),日常运行时设为只读”,防止非特权代码篡改程序;
- 敏感数据区只读:把存储密钥的RAM区域配置为“仅特权模式可写(初始化时),运行时设为只读”,非特权代码只能读取、无法修改;
- 禁止外设寄存器非特权写:所有外设寄存器默认设为“仅特权可写”,仅开放必要外设给非特权代码,防止恶意修改外设配置(如关闭看门狗)。
配置思路
// 示例:Flash程序区(0x08000000-0x080FFFFF,1MB)日常只读
MPU_InitStruct.BaseAddress = 0x08000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_1MB;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RO_UNPRIV_RO; // 全只读
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; // 允许执行(程序区)
四、核心场景4:适配不同内存类型的访问规则
适用场景
系统中混合使用Normal(SRAM)、Device(外设)、Strongly Ordered(特殊外设)内存;
需要优化内存访问性能(如开启缓存),同时保证外设访问的正确性(禁止缓存)。
具体例子
- SRAM开启缓存:Normal类型的SRAM配置为“可缓存、可缓冲”,提升访问速度;
- 外设寄存器禁止缓存:Device类型的外设寄存器配置为“不可缓存、不可缓冲”,确保读写操作立即生效(避免缓存导致的外设控制延迟);
- 严格有序内存保护:对DMA、以太网等高速外设的内存区域配置为“共享、不可缓存”,防止数据一致性问题。
配置思路
// 示例:外设寄存器区(0x40000000-0x400FFFFF,16MB)禁止缓存
MPU_InitStruct.BaseAddress = 0x40000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_16MB;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; // Device内存
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; // 禁止缓存
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; // 禁止缓冲
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_UNPRIV_RO;
五、什么时候可以不用MPU?
- 简单的裸机项目(如LED闪烁、串口打印),代码量小、无内存安全风险;
- 纯调试阶段,优先排查代码逻辑bug,再考虑MPU保护;
- 对性能要求极致且无安全需求(如纯算法运算),MPU的少量开销可忽略,但建议至少开启“禁止RAM执行”。
总结:MPU使用场景的核心判断
只要你的项目满足以下任一条件,就建议使用MPU:
- 代码量较大/多任务,担心内存错误导致崩溃;
- 有安全/稳定要求(商用、工业、功能安全);
- 使用RTOS,需要任务隔离;
- 存储敏感数据/运行第三方代码。
二、寄存器
MPU的寄存器说明截取自《STM32H7编程手册》

|
列名 |
注解 |
|
Address |
这个寄存器在处理器里的“地址”(你要操作这个寄存器,就得通过这个地址找到它) |
|
Name |
寄存器的名字(比如 |
|
Type |
寄存器的操作类型: |
|
Required privilege |
操作这个寄存器需要的权限:这里都是“Privileged(特权级)”——只有系统级代码(比如内核)能操作,普通应用程序没权限 |
|
Reset value |
处理器刚上电(复位)时,这个寄存器里默认的值 |
|
Description |
这个寄存器是干嘛用的 |
MPU的作用是 给内存划分“区域”,给每个区域设置权限(比如哪些代码能读/写/执行这块内存),下面是实现这个功能的核心寄存器:
MPU_TYPE(地址0xE000ED90)-
- 类型是“只读”,复位值是
0x00000800 - 作用:告诉你这个处理器的MPU有啥功能(比如支持多少个内存区域),是“MPU的身份信息”
- 类型是“只读”,复位值是
MPU_CTRL(地址0xE000ED94)-
- 类型是“读写”,复位值是
0x00000000(默认是0,代表MPU没开启) - 作用:控制MPU的开关(比如写1开启MPU,写0关闭)
- 类型是“读写”,复位值是
MPU_RNR(地址0xE000ED98)-
- 类型是“读写”
- 作用:选择你要配置的“内存区域编号”(比如你要配置第1个区域,就往这里写1)
MPU_RBAR(地址0xE000ED9C)-
- 类型是“读写”
- 作用:设置你选中的那个内存区域的起始地址(比如你要保护
0x20000000开始的内存,就把这个地址写进来)
MPU_RASR(地址0xE000EDA0)-
- 类型是“读写”
- 作用:设置这个内存区域的大小和权限(比如这块内存是1KB,还是只能读不能写)
剩下的MPU_RBAR_A1、MPU_RASR_A1这些,是前面MPU_RBAR/MPU_RASR的“别名寄存器”——功能和对应的主寄存器一样,只是地址不同,方便快速配置多个区域。
比较重要的两个寄存器是:MPU_RASR 和控制寄存器。
MPU_RASR:

控制寄存器:

三、代码
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2019-04-14 whj4674672 first version
*/
#include <rtthread.h>
#include "stm32h7xx.h"
/**
* @brief 设置某个区域的MPU保护
* @param baseaddr:MPU保护区域的起始地址(虚拟地址)
* @param size:MPU保护区域的大小(以32位的方式,单位为字节),可取的值参考:CORTEX_MPU_Region_Size
* @param rnum:MPU保护区编号,范围:0~7,总共可指定8个保护区,可取的值参考:CORTEX_MPU_Region_Number
* @param de:禁止指令执行;0,允许指令执行;1,禁止指令执行
* @param ap:访问权限,取值范围参考:可取的值参考:CORTEX_MPU_Region_Permission_Attributes
* @arg MPU_REGION_NO_ACCESS,无访问(特权&用户都不允许访问)
* @arg MPU_REGION_PRIV_RW,仅支持特权读写访问
* @arg MPU_REGION_PRIV_RW_URO,禁止用户写访问(特权可读写访问)
* @arg MPU_REGION_FULL_ACCESS,全访问(特权&用户都可访问)
* @arg MPU_REGION_PRIV_RO,仅支持特权读访问
* @arg MPU_REGION_PRIV_RO_URO,只读(特权&用户都不允许写)
* @arg 更多:STM32F7参考手册,4.6节Table 89.
* @param sen : 是否支持共享;MPU_ACCESS_NOT_SHAREABLE,不支持;MPU_ACCESS_SHAREABLE,支持
* @param cen : 是否支持cache;MPU_ACCESS_NOT_CACHEABLE,不支持;MPU_ACCESS_CACHEABLE,支持
* @param ben : 是否支持buffer;MPU_ACCESS_NOT_BUFFERABLE,不支持;MPU_ACCESS_BUFFERABLE,支持
* @retval 0,成功.
* 其他,失败.
*/
uint8_t mpu_set_protection(uint32_t baseaddr, uint32_t size, uint32_t rnum, uint8_t de, uint8_t ap, uint8_t sen, uint8_t cen, uint8_t ben)
{
MPU_Region_InitTypeDef mpu_region_init_handle;
HAL_MPU_Disable(); /* 设置MPU之前先关闭MPU,设置完成以后再使能MPU */
mpu_region_init_handle.Enable = MPU_REGION_ENABLE; /* 使能这个保护区 */
mpu_region_init_handle.Number = rnum; /* 设置保护区 */
mpu_region_init_handle.BaseAddress = baseaddr; /* 设置基地址 */
mpu_region_init_handle.DisableExec = de; /* 是否允许指令执行 */
mpu_region_init_handle.Size = size; /* 设置保护区大小 */
mpu_region_init_handle.SubRegionDisable = 0X00; /* 禁止子区域 */
mpu_region_init_handle.TypeExtField = MPU_TEX_LEVEL0; /* 设置类型扩展域为level0 */
mpu_region_init_handle.AccessPermission = (uint8_t)ap; /* 设置访问权限, */
mpu_region_init_handle.IsShareable = sen; /* 是否共享 */
mpu_region_init_handle.IsCacheable = cen; /* 是否开启cache? */
mpu_region_init_handle.IsBufferable = ben; /* 是否开启buffer */
HAL_MPU_ConfigRegion(&mpu_region_init_handle); /* 设置MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); /* 开启MPU */
return 0;
}
/**
* @brief 设置需要保护的存储器区域
* @param 无
* @note ʺ对部分存储器区域进行MPU保护,防止误操作导致硬件异常
* 因为LCD等不一定支持cache,避免存取显示数据错误等情况.
*/
int mpu_memory_protection(void)
{
/* 保护第1个DTCM,共128K字节 */
mpu_set_protection( 0x20000000, /* 基地址 */
MPU_REGION_SIZE_128KB, /* 长度 */
MPU_REGION_NUMBER1, 0, /* NUMER1,允许指令执行 */
MPU_REGION_FULL_ACCESS, /* 全访问 */
MPU_ACCESS_NOT_SHAREABLE, /* 禁止共享 */
MPU_ACCESS_CACHEABLE, /* 允许cache */
MPU_ACCESS_BUFFERABLE); /* 允许缓存 */
/* 保护第2个AXI SRAM,共512K字节 */
mpu_set_protection( 0x24000000, /* 基地址 */
MPU_REGION_SIZE_512KB, /* 长度 */
MPU_REGION_NUMBER2, 0, /* NUMER2,允许指令执行 */
MPU_REGION_FULL_ACCESS, /* 全访问 */
MPU_ACCESS_SHAREABLE, /* 允许共享 */
MPU_ACCESS_CACHEABLE, /* 允许cache */
MPU_ACCESS_NOT_BUFFERABLE); /* 禁止缓存 */
/* 保护第3个SRAM1~SRAM3,共512K字节 */
mpu_set_protection( 0x30000000, /* 基地址 */
MPU_REGION_SIZE_512KB, /* 长度 */
MPU_REGION_NUMBER3, 0, /* NUMER3,允许指令执行 */
MPU_REGION_FULL_ACCESS, /* 全访问 */
MPU_ACCESS_NOT_SHAREABLE, /* 禁止共享 */
MPU_ACCESS_CACHEABLE, /* 允许cache */
MPU_ACCESS_BUFFERABLE); /* 允许缓存 */
/* 保护第4个SRAM4,共64K字节 */
mpu_set_protection( 0x38000000, /* 基地址 */
MPU_REGION_SIZE_64KB, /* 长度 */
MPU_REGION_NUMBER4, 0, /* NUMER4,允许指令执行 */
MPU_REGION_FULL_ACCESS, /* 全访问 */
MPU_ACCESS_NOT_SHAREABLE, /* 禁止共享 */
MPU_ACCESS_CACHEABLE, /* 允许cache */
MPU_ACCESS_BUFFERABLE); /* 允许缓存 */
/* 保护MCU LCD屏所在的FMC存储区域,共64M字节 */
mpu_set_protection( 0x60000000, /* 基地址 */
MPU_REGION_SIZE_64MB, /* 长度 */
MPU_REGION_NUMBER5, 0, /* NUMER5,允许指令执行 */
MPU_REGION_FULL_ACCESS, /* 全访问 */
MPU_ACCESS_NOT_SHAREABLE, /* 禁止共享 */
MPU_ACCESS_NOT_CACHEABLE, /* 禁止cache */
MPU_ACCESS_NOT_BUFFERABLE); /* 禁止缓存 */
/* 保护SDRAM存储区域,共32M字节 */
mpu_set_protection( 0xC0000000, /* 基地址 */
MPU_REGION_SIZE_32MB, /* 长度 */
MPU_REGION_NUMBER6, 0, /* NUMER6,允许指令执行 */
MPU_REGION_FULL_ACCESS, /* 全访问 */
MPU_ACCESS_NOT_SHAREABLE, /* 禁止共享 */
MPU_ACCESS_CACHEABLE, /* 允许cache */
MPU_ACCESS_BUFFERABLE); /* 允许缓存 */
/* 保护第7个NAND FLASH存储区域,共256M字节 */
mpu_set_protection( 0x80000000, /* 基地址 */
MPU_REGION_SIZE_256MB, /* 长度 */
MPU_REGION_NUMBER7, 1, /* NUMER7,禁止指令执行 */
MPU_REGION_FULL_ACCESS, /* 全访问 */
MPU_ACCESS_NOT_SHAREABLE, /* 禁止共享 */
MPU_ACCESS_NOT_CACHEABLE, /* 禁止cache */
MPU_ACCESS_NOT_BUFFERABLE); /* 禁止缓存 */
return 0;
}
INIT_BOARD_EXPORT(mpu_memory_protection);
更多推荐



所有评论(0)