大家好!我是大聪明-PLUS!
"现在就发现问题总比让有缺陷的产品流入市场并毁掉许多无辜的生命要好。"

序幕
ARM Cortex-M(Arm v7-M)处理器有一个非常有用的功能,称为 MPU(内存保护单元)。让我们来了解一下它是什么,以及它的作用是什么。
定义
对齐地址是指可以取从零开始、具有一定周期值的地址。对齐地址的周期通常是 2 的幂:8;16;32;64;128;256;512;等等。
虚拟内存是一种计算机内存管理方法,它允许运行需要更多内存的程序,方法是自动将程序的各个部分在主存储器和辅助存储器之间移动。虚拟内存系统使用虚拟地址,这些虚拟地址会被转换为计算机内存中的物理地址。MMU 负责将虚拟地址转换为物理地址。在微控制器和需要极快运行速度或响应时间受限的专用系统(实时系统)中,很少使用虚拟内存。
寄存器是由一组单位触发器组成的阵列,用于存储自然数。寄存器通常为 32 位。
MMU(内存管理单元)是计算机硬件组件,负责管理中央处理器 (CPU) 的内存访问请求。其功能包括将虚拟内存地址转换为物理内存地址(即管理虚拟内存)、内存保护、缓存管理、总线仲裁,以及在较简单的计算机架构(尤其是地址总线宽度较小的计算机架构)中执行内存块切换。
异常是处理器中需要立即处理的事件。异常会停止主程序的执行,并调用单独的异常处理程序。然后,控制权将交还给主程序(主函数)。
MPU是一种提供内存保护的处理器硬件。它是 MMU 的简化版本,仅提供内存保护。MPU 不支持虚拟内存。当发生内存访问冲突时,MPU 会引发异常。
您需要文件中的什么内容?
要启动 MPU,您只需要这两个源文件。
|------------------------|--------|--------|
| 命名码头 | 页面 | 版本 |
| Arm v7-M 架构参考手册 | 858 | -- |
| ARM Cortex-M7处理器技术参考手册 | 145 | r0p2 |
执行
MPU 是ARM Cortex-M7 内核的一部分,或者更准确地说是ARMv7-M内核的一部分。

在物理内存中,MPU 寄存器位于地址0xE000_ED90。MPU 寄存器列表可在ARM Cortex-M7 处理器技术参考手册中找到。

**但是,位字段的详细信息在另一篇文档《Arm v7-M 架构参考手册》**中指定。MPU 支持 16 个间隔。
供应商的 SDK 中提供了 MPU 激活的示例:\Example\xxxxxx\MPU\Mpu_Example\Sources\main.c
使用 MPU 时,有一件非常重要的事情需要牢记。内存区域的起始地址必须与其自身大小对齐。例如,如果您的区域是 16 KB,则需要将其对齐到 16 KB。如果您的内存区域是 64 KB,则需要将其对齐到 64 KB。依此类推。如果不这样做,MPU 可能会自动将该区域"截断"到与其起始地址相对应的大小。以下是一个例子。
|------------|--------|---------|------------|----------------------------------------------|
| 基址 | 尺寸 | 对齐位 | 对齐地址 | 对齐地址 |
| 0x2100373c | 三十二 | 5 | 0x21003720 | 0010_0001_0000_0000_0011_0111_001 0_0000 |
| 0x2100375c | 64 | 6 | 0x21003740 | 0010_0001_0000_0000_0011_0111_01 00_0000 |
| 0x2100379c | 128 | 7 | 0x21003780 | 0010_0001_0000_0000_0011_0111_1 000_0000 |
| 0x00000000 | 65536 | 16 | 0x00000000 | 0000_0000_0000_0000_ 0000_0000_0000_0000 |
此要求在 Arm v7-M 架构参考手册中指定**。**

软件部分
MPU 由MPU_Region_InitTypeDef 类型的结构体配置,该结构体定义在XXX_core_common_feature.h文件中。SDK 中针对 MPU 的 API 并不丰富,仅分配了三个函数。
|----------|----------------------------------------------------|
| 函数名称 | 文件 |
| MPU_配置区域 | XXX_SDK_V2_3_2\模板\驱动程序\源代码\module_driver_mpu.c |
| MPU启用 | 模块驱动程序 |
| MPU_禁用 | 模块驱动程序 |
和往常一样:有 setter,但没有 getter。所以我不得不扩展 getter 并添加更多函数,至少提供一些诊断功能。
第一步是初始化 MPU 区域。

微处理器
初始化后,必须检查 MPU 设置是否已实际应用于硬件。

为了检查 MPU 是否正常工作,我准备了这个函数。它会尝试读取位于禁止的零地址的单个双字。
bool test_null_ptr(void) {
LOG_INFO(TEST, "%s()", __FUNCTION__);
bool res = true;
uint32_t* pAddr = NULL;
uint32_t val = *pAddr;
LOG_INFO(TEST, "Val:0x%08x", val);
return res;
}
在 ARM Cortex-M7 中,当 MPU 被触发时**,会触发** MemManage_Handler中断。触发 MPU 中断后,无法轻松退出。如果 MemManage_Handler 内部未禁用 MPU,MemManage_Handler 将被重复调用,从而阻塞主程序的执行。因此,必须在中断中暂时禁用 MPU。

好了,我们学习了如何捕获内存访问违规。太棒了!然而,一个新问题随之而来。我已经配置了所有 16 个 MPU 区域。MemManage_Handler 中断已触发。我该如何确定哪个区域触发了此中断?MPU 寄存器映射不会报告此情况。这个问题可以解决。一旦 MPU 中断触发,必须在 ISR 内部保存两个寄存器的值。
|-----------|------------------|------------|--------|
| 姓名 | 描述 | 地址 | 尺寸 |
| 最小二乘方误差 | MemManage故障地址寄存器 | 0xE000ED34 | 4 |
| CFSR | 可配置故障状态寄存器 | 0xE000ED28 | 4 |
| 多模频率反馈控制器 | 内存管理状态寄存器 | 0xE000ED28 | 1 |
它们位于 SCB 结构中。CFSR 寄存器包含 MMFSR 寄存器。MMFSR 的 MMARVALID 位表示在 MMFAR 寄存器中找到了有效地址。如果 MMFAR 为 1,则应读取 MMFAR。这将包含发生访问冲突的物理地址。MMFSR 寄存器只能用于检测某个区域(IACCVIOL 位)的代码执行。ARM 只有一个专用于读写的位------DACCVIOL。
检查阅读禁令
让我们尝试一次禁止读取。MPU 中断被触发,固件识别出发生中断的区域。

禁用使用 MPU 写入 RAM 内存
为了检查是否禁止写入 ICTM 内存,我使用了 test_mpu_ban_itcm_write 测试。shell 命令 tr mpu_ban_itcm_write 显示,尝试写入 ITCM 确实会触发 MemManage_Handler 中断。

禁用 RAM 内存中的代码执行
我准备了以下单元测试。ITCM_Fun 段的物理地址范围从 0x00008000 到 0x0000FFFF。MPU 禁止执行从 0x0000_0000 到 0x0000FFFF 的范围。这意味着 mpu_itcm_function 函数是从禁止范围的中间调用的。
__attribute__((section(".ITCM_Fun")))
static uint32_t mpu_itcm_function(const uint32_t in_value) {
uint32_t out_value = in_value + 1;
LOG_INFO(TEST,"%s(),In:%u,Out:%u",__FUNCTION__,in_value,out_value);
return out_value;
}
bool test_mpu_ban_exe(void) {
LOG_INFO(TEST, "%s():", __FUNCTION__);
bool res = true;
MpuHandle_t* Node = MpuGetNode(TEST_MPU_NUM);
ASSERT_NE(NULL, Node);
Node->it_done = false;
LOG_INFO(TEST, "mpu_itcm_function:0x%p", mpu_itcm_function);
uint32_t value = mpu_itcm_function(1);
(void) value ;
ASSERT_TRUE(Node->it_done);
ASSERT_EQ(2, value);
return res;
}
实验表明,MPU 不会禁止执行来自受限区域的代码。MPU 只是通知 MemManage_Handler 中断即将发生访问冲突 **。**但是,一旦 MPU 在中断中被禁用,代码本身在退出 MemManage_Handler 后就会继续不受干扰地执行。

如果从MemManage_Handler 中禁止执行的区域调用某个函数,那么就会触发HardFault_Handler。
看起来其余的单元测试都通过了。

好的。我们已经了解了如何启用 MPU。现在我们该如何做一些真正有用的事情呢?
使用 MPU 检测堆栈溢出
如果我们在堆和栈之间定义一个禁区,那么我们就可以根据MPU生成堆栈内存溢出警告。

我有一个名为try_recursion的函数,专门用于深入研究可用堆栈 RAM 内存的深度。它测量程序在内存中运行了多少字节。结果存储在 *stack_size 变量中。
static bool call_recursion(uint32_t stack_top_addr,
uint32_t cur_depth,
uint32_t max_depth,
uint32_t* stack_size) {
bool res = false;
if(cur_depth < max_depth) {
res = call_recursion(stack_top_addr,
cur_depth + 1,
max_depth,
stack_size);
} else if(cur_depth == max_depth) {
uint32_t cur_stack_use = stack_top_addr - ((uint32_t)&res);
*stack_size = cur_stack_use;
res = true;
} else {
res = false;
}
return res;
}
bool try_recursion(const uint32_t stack_top_addr,
const uint32_t max_depth,
uint32_t* const stack_size) {
bool res = false;
res = call_recursion(stack_top_addr, 0, max_depth, stack_size);
LOG_INFO(CORE, "Depth:%u,StackSize:%u,byte", max_depth, *stack_size);
return res;
}
当max_depth值足够高时,堆栈帧开始与堆栈下方的 MPU 区域重叠。实际情况就是这样。实际上,我在 LD 脚本中为堆栈指定了 4k 字节。一切都匹配。

您可以用类似的方式监视堆内存。

您还可以通过尝试分配和写入过多的堆内存在 UART-CLI 终端中检查这一点。

因此事实证明,MPU 负责守护堆栈和堆内存。

您甚至可以创建两组 MPU 区域:警告区域(黄色)和错误本身的区域(红色)。

ARM Cortex-M7 MPU 的缺点
1------地址需要与区域大小对齐。2------没有write_only
模式。

3--需要手动通过编程确定内存入侵发生在哪个特定区域。
4------MPU 区域大小只能是 2 的幂。
5--当触发MPU中断时,不清楚触发中断的原因:是触发了读禁止,还是触发了写禁止。
MPU 应用程序
如何使用 MPU?
- 可以实现堆栈 RAM 内存保护。使用 MPU 可以检测堆栈 RAM 溢出。2
. 固件保护。可以禁止写入包含可执行固件代码的地址。3
. 加密密钥读取保护。4
. 可以将整个 RAM 内存区域映射为可读写。如果不需要从 RAM 执行代码,可以设置执行禁止位 (XN)。
结果
我们能够启用 ARM Cortex-M7 上的 MPU。我们能够针对未经授权的物理内存区间读写操作生成中断。
可以使用 MPU 来检测微控制器中堆栈溢出的事实。