文章目录

  • 前言
  • 何为MPU
  • MPU适用场景探讨
  • MPU相关寄存器介绍
  • MPU的使用和配置
  • 存储器屏障
  • 后记


前言

之前在一段时间内接触过MPU,当时由于要完成任务,所以对MPU没有做过多的研究,并且在网上搜索关于MPU的资料,能把他介绍的很全面的很少,下面是我个人结合ARM的官方文档以及自己整理的一些资料,以Cortex-M0+架构为基础讲解MPU,希望能对大家有所帮助。

何为MPU

MPU意思是Memory Protect Unit,即为存储保护单元,它是位于存储器内部的一个可编程的区域,定义了存储器的属性和存储器的访问权限。MPU不会提升嵌入式应用的性能,而是用于系统中问题的检测(比如试图访问非法或者不允许的存储器位置所导致的应用错误)。如果检测到有错误,则会触发HardFault异常。实际上,许多微控制器用不到MPU,但MPU可以提高嵌入式系统的健壮性,在如下的情况中使得系统更加安全:

  • 避免应用任务破坏其他任务或者OS内核使用的栈或数据存储器。
  • 避免非特权任务访问对系统可靠性和安全性很重要的外设。
  • 将SRAM或RAM定义为不可执行的(永不执行,XN),可以防止代码注入攻击。

还可以利用MPU定义其他存储器属性,例如可被输出到系统级的缓存单元或存储器 控制器的可缓存性。MPU默认是禁止的,此时对于存储器来说,其使用的是默认的存储器属性。

MPU适用场景探讨

其实对于简单的应用,比如IO控制,不太可能会用到MPU,除非使用的微处理器中存在系统级的缓存且需要MPU对其进行定义。
物联网,如果应用是和网络相关的东西,或者应用面临着无法信任的通信接口,则MPU有助于提高安全性。例如:将用于通信缓冲的存储器区域定义为不可执行的地址区域后,就可以防止代码注入攻击。
工业控制领域,如果应用需要很高的可靠性,则MPU可为多任务系统中栈加以限制,以检测一些意想不到的错误。
汽车应用,MPU常用于汽车部件中。软件部件间不能互相有接口,所以需要MPU处理存储器区域。

我们可以将MPU的应用分为以下几类:

  • 安全管理:
    未受信任或者风险较高的软件部件,应该运行在非特权等级。MPU可以限制这些部件访问的存储器空间。
    用作通信缓冲RAM空间中可能包含通信注入的恶意代码,MPU可以将存储器空间定义为不可执行。
  • 系统可靠性:
    在多任务的系统中,MPU可以定义应用任务栈的合法存储器空间,使得任务不会破坏其他任务或OS数据栈的空间。
    若应用具有较高的安全需求,则MPU可以将栈空间最后定义为不可访问的存储器空间,这样可以检测出栈溢出。
    有些程序可能需要将代码复制到SRAM中执行,或者将向量表复制到SRAM中以提高访问速度。在复制完代码或者向量之后,存储器空间可以被定义为只读的,防止存储器空间被意外的修改。
  • 存储器属性管理:
    可以利用MPU定义可被缓存的存储器空间,以及存储策略(写通vs写回)。
    利用MPU配置覆盖掉某个存储器空间的默认配置。
    总之,我们需要根据具体的项目需要,选择MPU是默认配置还是需要改一些配置,这样才能使我们的应用更加符合要求。

MPU相关寄存器介绍

Cortex-M0+处理器中的MPU最多支持8个可编程的存储器空间以及1个可选的背景区域。每个可编程的区域都有自己的起始地址、大小以及属性设置。对于ARM-v6M和ARM-v7M架构,MPU的区域可以重叠,如果某存储区域位于两个已编程的MPU区域中,则其存储属性和权限会基于编号更大的那个区域,处理器在执行不可屏蔽中断(NMI)或HardFault处理时,MPU访问权限会被忽略。例如,可以将SRAM栈底的一小块SRAM空间定义为不可执行,将MPU用作栈溢出的检测机制。当栈到达边界的时候,HardFault可以忽略MPU限制并在错误的处理中使用预留的SRAM空间。
下面介绍MPU相关寄存器。
1.MPU类型寄存器
其可用于确定MPU是否存在,若DREGION区域读出为0,则表明MPU不存在。
(MPU->TYPE, 0xE000ED90)


名称

类型

复位值

描述

23:16

IREGION

R

0

本MPU支持的指令区域数,由于ARM-v6M架构使用统一的MPU,其总数为0

15:8

DREGION

R

0或8

MPU支持区域数

0

SEPARATE

R

0

由于MPU为统一的,其总为0

2.MPU控制寄存器
其有3个控制位,复位后,该寄存器数值为0,这样会禁止MPU。若要使能MPU,软件需要首先设置MPU区域,然后设置控制寄存器中的ENABLE位。
(MPU->CTRL, 0xE000ED94)


名称

类型

复位值

描述

2

PRIVDEFENA

R/W

0

特权等级默认的存储器映射使能,设置为1且MPU使能时,同背景区域一样,特权访问会使用默认的存储器映射,若未设置该位,则背景区域禁止且任何不在使能区域范围内的访问都将引发错误

1

HFNMIENA

R/W

0

若设置为1,则MPU在硬件错误处理和不可屏蔽中断(NMI)处理中也是使能的,否则在硬件错误以及NMI中不使能

0

ENABLE

R/W

0

若设置为1则使能MPU

说明:MPU控制寄存器中的PRIVDEFENA位用于使能背景区(区域“-1”),若未设置其他区域,那么通过PRIVDEFENA,特权程序可以访问所有的存储器位置,只有非特权程序才会被阻止。如果设置并使能其他的MPU区域,则背景区域可能会被覆盖。
HFNMIENA用于定义NMI、HardFault异常执行期间或FAULTMASK置位时MPU的行为。MPU在这些情况下默认被禁止,即便是MPU设置的不正确,它也可以使HardFault和NMI异常处理正常执行。
3.MPU区域编号寄存器
在设置每个区域前,写入该区域以选择编程区域。
(MPU->RNR, 0xE000ED98)


名称

类型

复位值

描述

7:0

REGION

R/W


选择待编程的区域

4.MPU区域基地址寄存器
每个区域的起始地址在MPU区域基地址寄存器中,利用VALID和REGION域,可以跳过设置MPU区域编号寄存器这一步,可以降低程序的代码复杂度。
(MPU->RBAR, 0xE000ED9C)


名称

类型

复位值

描述

31:N

ADDR

R/W


区域基地址,N取决于区域的大小,例如64KB大小区域的基地址为[31:16]

4

VALID

R/W


若为1,则bit[3:0]定义的REGION会用在编程阶段,否则会使用MPU区域编号寄存器选择的区域

3:0

REGION

R/W


若VALID为1,该域会覆盖MPU区域编号寄存器,否则会被忽略。由于Cortex-M3和Cortex-M4的MPU支持8个区域,当REGION域大于7时,会忽略掉区域编号覆盖。

5.MPU区域基本属性和大小寄存器
此外需要配置每个区域的属性。
(MPU->RASR, 0xE000EDA0)


名称

类型

复位值

描述

31:29

保留



28

XN

R/W

0

指令访问禁止(1=禁止从本区域取指令,强行访问会引起存储器管理错误)

27

保留



26:24

AP

R/W

000

数据访问允许域

23:22

保留



21:19

TEX

R/W

000

类型展开域,在此架构中始终为0

18

S

R/W


可共用

17

C

R/W


可缓存

16

B

R/W


可缓冲

15:8

SRD

R/W

0x00

子区域禁止

7:6

保留



5:1

REGIO大小

R/W


MPU保护区域大小

0

ENABLE

R/W

0

区域使能

其中REGION SIZE域决定了区域的大小,例如:

REGION大小

大小

b00111

256字节

b01000

512字节

b01010

2KB



b11100

512MB



b11111

4GB

子区域禁止用于将一个区域分为8个相等的子区域并定义每个部分为使能或禁止。若一个子区域被禁止且和另一区域重叠,则另一区域的访问规则会起作用。若子区域禁止但并未和其他区域重叠,则对该存储器区域访问会导致HardFault异常。
数据访问权限(AP)域(bit[26:24])定义了区域的AP,如下所示:

AP数值

特权访问

用户访问

描述

000

无访问

无访问

无访问

001

R/W

无访问

只支持特权访问

010

R/W

R

用户程序的写操作会导致异常

011

R/W

R/W

全访问

100

无法预测

无法预测

无法预测

101

R

无访问

只支持特权读

110

R

R

R

111

R

R

R

XN(永不执行)(bit[28])决定是否允许从该区域取指。
TEX(类型扩展)、S(可共享)、B(可缓冲)、C(可缓存)域(bit[21:16]),这些属性在每次寄存器和数据访问的时候都会被输出到总线系统,并且该信息可被写缓冲或缓存单元等总线系统使用。
而对于微控制器来说,只有B(可缓冲)属性会影响到处理器中的写缓冲。
若使用的微控制器支持设备缓存,则多数情况下可被配置为如下情况:

类型

存储器类型

常用存储器操作

ROM,Flash(可编程存储器)

普通存储器

不可共用,写通,C=1,B=0,TEX=0,S=0

内部SRAM

普通寄存器

可共用,写通,C=1,B=0,TEX=0,S=0

外部RAM

普通寄存器

可共用,写回,C=1,B=1,TEX=0,S=1

外设

设备

可共用,设备,C=0,B=1,TEX=0,S=1

其中,可共享属性对于具有缓存的多处理器系统非常重要,若传输标志为可用的,则缓存系统需要额外一些工作以确保不同的处理期间的缓存数据的一致性。单处理系统一般用不到可共享属性。

MPU的使用和配置

在我们使用MPU之前,需要确定程序或应用程序访问的存储器区域,其中包括:

  • 中断处理和OS内核在内的特权应用代码,一般来说只支持特权访问
  • 中断处理和OS内核在内的特权应用使用的数据存储器,一般只支持特权访问
  • 非特权应用程序代码,全访问
  • 非特权应用栈等数据存储器,全访问
  • 中断处理和OS内核在内的特权应用使用外设,只支持特权访问
  • 用于非特权的外设,全访问。
    对于存储器来说,MPU的要求为:
  • 存储器大小必须为2的整数次方,在256KB到4GB之间。
  • 存储器起始地址必须对齐到区域大小的整数倍。例如我的区域大小为4KB(0x100),那么我的起始地址必须为N*0x100程序示例
    这里我只写一些通用的可配置的一些子函数,具体如何配置还要根据不同的使用情况来,但大致都是这个流程。
//使能MPU时可以带有输入配置项
void mpu_enable(uint32_t options)
{
	MPU->CTRL = MPU_CTRL_ENABLE_MASK | options; //禁止MPU
	__DSB();  //确保MPU设置生效
	__ISB();  //利用更新后的设置
	return;                  
}

//禁止MPU
void mpu_disable(void)
{
	__DMB(); //确保之前传输全部完成
	MPU->CTRL = 0;  //禁止MPU
	return;
}

//禁止区域函数(0到7)
void mpu_region_disable(uint32_t region_num)
{
	MPU->RNR = region_num;
	MPU->RBAR = 0;
	MPU->RASR = 0;
	return;
}

//使能区域函数
void mpu_region_config(uint32_t region_num, uint32_t addr, uint32_t size, uint32_t attributes)
{
	MPU->RNR = region_num;
	MPU->RBAR = addr;
	MPU->RASR = size | attributes;
	return;
}

存储器屏障

在上述的代码示例中,我们使用到了存储器屏障指令:

  • DMB(数据存储器屏障),在禁止MPU前使用,确保数据的传输不会重新排序,并且如果有未完成的传输,会等到传输完成之后在写入MPU相应寄存器。
  • DSB(数据同步屏障),在使能MPU后使用,确保接下来的ISB指令只会在写入MPU控制寄存器结束后才执行,可以确保后续的数据传输使用新的MPU设置。
  • ISB(指令同步屏障),用于DSB之后,确保处理器流水线被清空且接下来指令利用更新后的MPU设置被重新读出。
    建议使用上述指令,虽然处理器的流水线相对简单,忽略这些存储器特性也不会产生什么问题,但从软件可移植的角度来说,这样可以使软件可移植性得到提高。适用于其他Cortex-M系列处理器。
    若MPU应用于嵌入式OS中,且MPU配置在上下文切换操作中完成(一般是PendSV异常处理),则异常入口和退出流程具有ISB的效果,所以从这个角度看就不需要ISB指令了。