发新帖本帖赏金 80.00元(功能说明)我要提问
返回列表
打印
[APM32F4]

探索MPU:功能解析、寄存器配置与编程应用

[复制链接]
1850|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
DKENNY|  楼主 | 2024-8-15 16:23 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 DKENNY 于 2024-8-15 16:43 编辑

#申请原创# @21小跑堂


最近学习了有关MPU的内容,本帖将对MPU的功能,相关寄存器的配置,以及在实际编程中如何运用MPU这三个方面作一个简单的分享。

## MPU 的功能
内存保护单元(MPU)能够将内存划分为多个区域,并为每个区域设定不同的访问权限,从而提升系统的可靠性。它的主要用途包括:
  - 区分特权用户和普通用户的访问区域,这样可以提高操作系统的稳定性。
  - 设置只读区域,以防止重要数据被误修改。
  - 监测堆和栈的溢出情况,以确保系统安全。

## MPU相关寄存器(基地址:0xE000ED90)
MPU配备了一系列寄存器。接下来,我们将结合这些寄存器详细阐述MPU的功能。寄存器组如下表所示。
寄存器名
类型
复位值
寄存器功能描述
MPU_TYPE
RO
A
MPU 类型寄存器
MPU_CTRL
RW
0x00000000
MPU控制寄存器
MPU_RNR
RW
-
MPU区域号寄存器(region号)
MPU_RBAR
RW
-
MPU区域基地址寄存器
MPU_RASR
RW
-
MPU区域属性和大小寄存器
MPU_RBAR_A1
RW
-
MPU_RBAR的别名1
MPU_RASR_A1
RW
-
MPU_RASR的别名1
MPU_RBAR_A2
RW
-
MPU_RBAR的别名2
MPU_RASR_A2
RW
-
MPU_RASR的别名2
MPU_RBAR_A3
RW
-
MPU_RBAR的别名3
MPU_RASR_A3
RW
-
MPU_RASR的别名3

下面详细介绍各个寄存器。

### MPU 类型寄存器(MPU_TYPE,地址:0xE000ED90)
名称
位段
功能描述

Bits[31:24]
保留
IREGION
bits[23:16]
MPU 支持的指令 region 数量。因为 ARMv7‐M
只使用单个统一的 MPU,此位段一直为0
DREGION
bits[15:8]
MPU支持的区域数量。如果该字段的读数为0,则处理器不实现MPU

Bits[7:1]
保留位
SEPARATE
bit[0]
是否支持单独的指令和数据地址映射。

MPU寄存器是只读的,我们主要关注DREGION位。通过检查这个位,我们可以确定是否启用了MPU。如果这个字段的读取值为零,那么就意味着MPU没有被使用。如果MPU被启用,那么通常情况下,它会返回MPU支持的区域数量,Cortex_M4最多可以支持8个区域。

可使用jlink读取该寄存器地址,以查看该芯片内核是否支持MPU,如下是APM32F407的读取情况:


可以看到读取的数据是0x800,对应的DREGION值为8,说明MPU支持的region最大数量为8。

### MPU控制寄存器(MPU_CTRL,地址:0xE000ED94)
名称  
位段
功能描述

Bits[31:3]
保留
PRIVDEFENA
bit[2]
设置特权访问模式下MPU的策略
1=启用默认内存映射作为特权访问的背景区域。  
0=禁用默认内存映射,超出制定区域的任何数据或指令访问都将引起错误。
HFNMIENA
bit[1]
设置在不可屏蔽中断(NMI)和硬 件中断中MPU策略。  
1= 在不可屏蔽中断和硬件中断服务函数  中MPU依然有效  
0=在不可屏蔽中断和硬件中断服务  函数中MPU依然失效
ENABLE
bit[0]
MPU 使能位,1=使能,0=不使能

1. 寄存器概述
   - 在MPU_CTRL寄存器中,ENABLE和HFNMIENA位的作用已有明确说明,因此不再详细讨论。

2. 重点讨论PRIVDEFEN位
   - Cortex内核具有两种执行模式:特权模式和用户模式。
     - 特权模式:具有更高的访问权限。
     - 用户模式:权限较低,限制较多。

3. MPU的配置
   - PRIVDEFENA位用于配置特权模式下的MPU访问策略。
   - 当MPU启用时,内存空间可以被划分为1到8个区域(region0~region7)。
   - 一些内存区域可能未被划分,这些未划分的区域称为背景区域(region-1)。

4. PRIVDEFENA位的作用
   - 当PRIVDEFENA位设为1时:
     - 允许特权模式访问背景区域(region-1)。
   - 当PRIVDEFENA位设为0时:
     - 禁止特权模式访问背景区域(region-1)。

### MPU区域号寄存器(MPU_RNR,地址:0xE000ED98)
名称  
位段
功能描述

Bits[31:8]
保留
REGION
bits[7:0]
指定设置的内存区域

1. MPU的区域配置
   - MPU支持配置1到8个区域(region)。
   - 每个区域的设置是独立的,可以根据需要进行个别配置。

2. 配置步骤
   - 在进行区域配置之前,必须先通过MPU_RNR寄存器选择要设置的区域编号。
   - 选择好区域编号后,再对MPU_RBAR和MPU_RASR寄存器进行配置。

### MPU区域基地址寄存器(MPU_RBAR,地址:0xE000ED9C)
名称
位段
功能描述
ADDR
bits[31:5]
区域的基地址
VALID
bit[4]
决定REGION字段设置的区域编 号是否覆盖MPU_RNR寄存器设置的区域编号
REGION
bits[3:0]
MPU 区域编号复写字段

1. ADDR位段
   - 位段:bits[31:5]。
   - 功能描述:存储区域的基地址。

2. VALID位段
   - 位段:bit[4]。
   - 功能描述:决定REGION字段设置的区域编号是否覆盖MPU_RNR寄存器设置的区域编号。

3. REGION位段
   - 位段:bits[3:0]。
   - 功能描述:用于复写MPU区域编号。

4. 寄存器的便利性
   - 通过设置REGION为区域编号,VALID为1,提供了一种更为便捷的方式来修改区域编号。
   - 这种方式可以更快地设置区域编号。

### MPU区域属性和大小寄存器(MPU_RASR, 地址:0xE000EDA0)
名称
位段
功能描述

bits[31:29]
保留
XN
bits[28]
禁止取指

bits[27]
保留
AP
bits[26:24]
访问权限

bits[23:22]
保留
TEX
bits[21:19]
扩展类型
S
bits[18]
是否可共享,1=可共享,0=不可 共享
C
bits[17]
(Cachable)可缓存,1= 可缓存,0=不可缓存。
B
bits[16]
(Bufferable)可传冲
1=可缓冲,0=不可缓冲
SRD
bits[15:8]
子区域使能位,对于256字节或更大的区域,该字段的每个位控制是否启用八个相等的子区域

Bits[7:6]
保留位
SIZE
bits[5:1]
区域的大小
ENABLE
bit[0]
区域使能位,1,使能区域。0,禁用区域

1. MPU_RASR寄存器简介
  - 功能:MPU_RASR寄存器主要负责配置MPU区域的属性。
  - 重要性:这是一个关键的过程,涉及许多需要深入理解的概念。

2. SRD位的作用
  - 子区域配置:SRD位用于配置子区域。
  - 区域划分:
    - 所有超过128字节的区域被划分为8个等大小的子区域。
    - 每个子区域的大小相同。

3. SRD的位解释
  - 位段:
    - SRD寄存器的8位:从第8位到第15位。
  - 控制功能:
    - 如果设置为1:启用相应的子区域(region)。
    - 如果设置为0:禁用相应的子区域(region)。

区域属性B、C、S、TEX的设置,详细解释如下表。
TEX
C
B
存储器类型
描述
可共享
000
0
0
严格按照顺序
严格按照顺序
可共享
000
0
1
设备
共享的设备
可共享
000
1
0
普通
外部内存或内部内存,写通型内存,没有写分配
由S位决定
000
1
1
普通
外部内存或内部内存,写回型内存,没有写分配
由S位决定
001
0
0
普通
外部内存或内部内存,Non-cachable型内存
由S位决定
001
0
1
保留
保留,
保留
001
1
0
IMPL EMENTATI ON DEFINED
IMPL EMENTATI ON DEFINED
IMPL EMENTATION
DEFINED
001
1
1
普通
外部内存或内部内存,写回型内存,有读、写分配
由S位决定
010
0
0
设备
不可共享设备
不可共享
010
0
1
保留
保留
保留
010
1
X
保留
保留
保留
011
X
X
保留
保留
保留
1BB
A
A
普通
带缓存的内存,AA,针对内部内存,BB针对外部内存,
由S位决定

1. 内存特性
   - 写通型内存(Write-Through):
     - 当Cache被激活,且内存A被设定为写通型时,CPU向内存A写入数据。如果Cache中已经为内存A的数据分配了对应空间,那么数据会同时写入Cache和内存。

   - 写回型内存(Write-Back):
     - 当Cache被激活,且内存A被设定为写回型时,CPU向内存A写入数据。如果Cache中已经为内存A的数据分配了对应空间,那么数据会被写入Cache,但不会立刻写入内存。

   - 非缓存(Non-cachable):
     - 这种类型的内存在CPU进行读写操作时不会经过Cache。

2. Cache策略
   - 读分配(Read Allocate)
     - Cache被激活时会启用。意味着当CPU需要读取内存B的数据时,如果在Cache中找不到,则会直接从内存中读取,并在Cache中备份。

   - 写分配(Write Allocate)
     - 功能与读分配类似,即在Cache被激活时,如果进行写操作且Cache中没有对应数据,则会从内存中读取数据到Cache后再进行写入。

3. TEX配置:
   - 当TEX的最高位设为1时,可以分别配置外部内存(片外内存)和内部内存(片内内存)的属性。
   - 此时,TEX的后两位(BB)用于设定外部内存的属性,而C和B位(AA)用于设定内部内存的属性。

无论是AA还是BB,它们的每个数值都有相同的含义,如下表所示。
AA 或 BB编码
可缓存策略
00
不可缓存
01
写回,读分配和写分配
10
写通,读分配,无写分配
11
写回,读分配,无写分配

访问权限AP,字段AP用于设置访问权限,同样我们用一张表来说明该位,如下表所示。
AP[2:0]  
特权用户访问权限
普通用户访问权限
注意事项
000
不可访问
不可访问
任何访问都会生成权限错误
001
Read/write
不可访问
只允许特权用户访问
010
Read/write
Read-only
非特权用户只读
011
Read/write
Read/write
完全访问
100
UNPREDICTABLE
UNPREDICTABLE
保留
101
Read-only
不可访问
特权用户只读
110
Read-only
Read-only
只读
111
Read-only
Read-only
只读

MPU的设置的主要任务就是配置MPU_RASR寄存器,MPU_RASR寄存器寄存器主要分为区域属性设置和访问权限设置

## MPU的使用
MPU的配置流程可以参考如下的流程图:


如下是一个简单的MPU配置例程代码,配套使用的开发板是APM32F407IG。

Main函数实现:

void MPU_Config(void)
{
    ARM_MPU_Disable();
    MPU->RBAR = ARM_MPU_RBAR(0,0x08004000);
    MPU->RASR = ARM_MPU_RASR(0,ARM_MPU_AP_PRO,0,0,1,1,0,ARM_MPU_REGION_SIZE_1KB);

    ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);

    NVIC_EnableIRQ(MemoryManagement_IRQn);
}

void USARTInit(void)
{
    /* USART Initialization */
    USART_Config_T usartConfigStruct;

    /* USART configuration */
    USART_ConfigStructInit(&usartConfigStruct);
    usartConfigStruct.baudRate = 115200;
    usartConfigStruct.mode = USART_MODE_TX_RX;
    usartConfigStruct.parity = USART_PARITY_NONE;
    usartConfigStruct.stopBits = USART_STOP_BIT_1;
    usartConfigStruct.wordLength = USART_WORD_LEN_8B;
    usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;

    /* COM1 init*/
    APM_COMInit(COM1, &usartConfigStruct);
}

/*!
* [url=home.php?mod=space&uid=247401]@brief[/url]    Main program
*
* @param     None
*
* @retval    None
*/
int main(void)
{
    USARTInit();

    MPU_Config();

    *(uint32_t *)0x08004000 = 0x12;

    while (1)
    {
    }
}
其中ARM相关的MPU操作函数在其相关的内核文件中,可以自行去了解,这里就不介绍了。

中断服务函数实现:
/*!
* [url=home.php?mod=space&uid=247401]@brief[/url]    This function handles Memory Manage exception
*
* @param     None
*
* @retval    None
*
*/
void MemManage_Handler(void)
{
    /* Go to infinite loop when Memory Manage exception occurs */
    while (1)
    {
    }
}

其中涉及到宏定义参数介绍如下:
#define ARM_MPU_RBAR(Region, BaseAddress)
- Region,MPU区域编号,最多可以设置8个Region ,取值范围为(0到7)
- BaseAddress,区域的起始地址。

#define ARM_MPU_RASR(DisableExec, AccessPermission, TypeExtField, IsShareable, IsCacheable, IsBufferable, SubRegionDisable, Size)
宏ARM_MPU_RASR与宏ARM_MPU_RBAR分别用于设置MPU_RBAR寄存器和MPU_RASR寄存器。我们知道宏定义中每个参数的含义即可,其他工作有宏定义自动完成。宏ARM_MPU_RASR的参数介绍如下:
- DisableExec,禁用区域(region),1=禁用指定的区域,0=启用指定的区域。
- AccessPermission,设置数据访问权限。

#define ARM_MPU_AP_NONE 0u
#define ARM_MPU_AP_PRIV 1u
#define ARM_MPU_AP_URO  2u
#define ARM_MPU_AP_FULL 3u
#define ARM_MPU_AP_PRO  5u
#define ARM_MPU_AP_RO   6u
通过宏定义的名字我们可以知道设置的访问权限。
  - TypeExtField、IsShareable、IsCacheable、IsBufferable,依次设置MPU_RASR寄存器的TEX、S、C、B字段。
  - SubRegionDisable,禁用次区域。1=禁用,0=启用次区域。
  - Size,区域大小。

在配置MPU(Memory Protection Unit)区域时,有两个关键点需要注意:

1. 区域重叠:如果两个或多个MPU区域有重叠部分,那么区域号较大的设置将会覆盖区域号较小的设置。这是因为在处理重叠区域时,MPU会优先考虑区域号较大的设置。例如,如果区域0和区域1有重叠,那么在重叠部分,区域1的设置将会被使用。

2. 区域号的最大值:在配置MPU区域时,区域号的值必须在有效范围内,通常是0到7(包含)。如果超过这个范围,可能会导致不可预测的错误。这是因为MPU硬件只设计了固定数量的区域(在这个例子中是8个),超出这个范围的区域号是无效的。

总的来说,这两点都是为了确保MPU配置的正确性和系统的稳定性。

上面的例程代码主要实现了以下功能:

- MPU_Config(void):这个函数用于配置内存保护单元(MPU)。在函数内部,首先禁用了MPU,然后设置了一个具有特定属性的内存区域,这个区域的基地址是0x08004000,大小是1KB,访问权限被设置为仅允许特权级别的访问。最后,启用了MPU和默认的内存管理错误中断。

- USARTInit(void):这个函数用于初始化USART(通用同步异步收发器)。在函数内部,首先定义了一个USART的配置结构体,然后初始化了这个结构体,并设置了一些参数,如波特率、模式、奇偶校验、停止位、字长和硬件流控制。最后,使用这个配置结构体初始化了COM1。

- main(void): 这是程序的主函数。在函数内部,首先调用了USARTInit()和MPU_Config()函数,然后试图向地址0x08004000写入一个值。由于这个地址被MPU设置为只允许特权级别的访问,所以这个写操作会引发内存管理错误中断,然后程序将会进入MemManage_Handler()函数。

- MemManage_Handler(void):这是一个中断处理函数,用于处理内存管理错误中断。在这个函数内部,程序进入了一个无限循环,也就是说,一旦发生了内存管理错误中断,程序就会停止在这个函数中。

实验现象
由于在main()函数中尝试向受保护的内存地址写入数据,这将触发内存管理错误中断,然后程序将进入MemManage_Handler()函数,并在该函数中无限循环。因此,程序的实际效果将是一旦开始运行,就会“冻结”在MemManage_Handler()函数中。



如上图的实验现象正好符合我们设置的需求。

## 问题
1、如何区分特权用户和普通用户的访问区域?
  具体来说,我们可以通过MPU给特权用户和普通用户设定不同的内存区域,分配不同的访问权限。比如,特权用户可以访问一个既能读也能写的内存区域,而普通用户只能访问一个只能读或只能写的区域。这样,如果普通用户试图进入特权用户的内存区域,系统就会直接拒绝这个请求,保护特权用户的数据不被非法访问。

  在实际编程时,我们需要写一些代码来设置MPU,来区分不同用户可以访问的区域。这通常涉及到对MPU内部设置的读写操作,以定义哪些区域允许访问,哪些区域被限制。

2、如何使用MPU监测堆和栈的溢出情况以确保系统安全?
  - 1.配置MPU区域
    - 划分内存区域: 将内存划分为多个区域,分别用于极其重要的任务(如操作系统、关键应用程序、堆、栈等)。
    - 设置属性: 为每个区域设置访问权限。例如,可以将堆和栈区域设为可读写,但不可执行。其他重要区域,例如程序代码区域,应该为只读,并且不可写。
  - 2.监测堆和栈的使用
    - 栈溢出监测: 在栈的底部设置一个保护区域,通常是一个无效的内存地址。通过监测这些地址的访问情况,可以检测到栈的溢出。如果系统访问了保护区域,可以及时报警并采取措施(如重启系统)。
    - 堆溢出监测: 在堆内存分配中,可以在每次分配之前和之后插入控制代码,以检查当前的堆指针和堆边界。如果堆指针越界并触及保护区域,则表示发生了堆溢出,可以记录相关信息并进行处理。
  - 3.硬件异常处理
    - 异常处理: 通过MPU,如果系统接收到访存错误的异常(例如访问被保护的内存区域),可以触发相应的中断服务程序(ISR)。在ISR中,可以实现错误日志记录、系统重启或进入安全模式等功能。

## 总结
  这篇帖子主要介绍了内存保护单元(MPU)的核心功能及其相关寄存器的详细信息。MPU 是一种硬件组件,主要用于系统内存管理,以提高系统的安全性和稳定性。MPU通过为不同的内存区域设置访问权限,限制应用程序对系统内存的访问,从而防止错误或恶意代码的运行对系统造成影响。
  同时,内存保护单元(MPU)是现代计算系统中不可或缺的组成部分,它通过提供灵活的内存访问控制和保护机制,增强了系统的安全性和稳定性。合理配置MPU对于构建高可靠、高安全性的嵌入式和实时系统至关重要。

  以上就是本次分享的全部内容,有关MPU的相关内容,欢迎各位讨论交流。

  附件:
   MPU_Example.zip (786.79 KB)



使用特权

评论回复

打赏榜单

21小跑堂 打赏了 80.00 元 2024-08-16
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2024-8-16 17:00 回复TA
一篇详解MPU的功能和相关寄存器理论知识,并通过简单点案例讲解编程时的应用方法。 
沙发
kai迪皮| | 2024-8-20 13:53 | 只看该作者
学到了,顶好文

使用特权

评论回复
发新帖 本帖赏金 80.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

35

主题

60

帖子

6

粉丝