在嵌入式系统开发中,内存访问的对齐性是一个重要的概念。本文将给各位简单分析字节对齐访问与非对齐访问的差异,并介绍在APM32F407平台上对`CCR.UNALIGN_TRP`设置进行的实验。希望通过我的小小文章帮助您了解如何优化内存访问以提升系统性能和稳定性。
1 什么是字节对齐访问?
字节对齐访问是指数据在内存中的地址满足特定的对齐要求。例如,对于32位的数据(如`uint32_t`),其内存地址需要是4的倍数。对齐访问的优点包括:
1. 性能优化:处理器通常对对齐访问进行了优化,可以在一个时钟周期内完成。
2. 数据一致性:对齐访问减少了数据不一致性的风险,尤其是在多线程或多核环境下。
3. 减少异常:对齐访问避免了由于未对齐访问导致的异常。
2 什么是非对齐访问?
非对齐访问是指数据在内存中的地址不满足对齐要求。例如,一个32位的数据存储在地址`0x20000001`。非对齐访问的潜在问题包括:
1. 性能开销:处理器需要多个时钟周期来处理非对齐访问,降低了系统性能。
2. 硬件限制:某些处理器完全不支持非对齐访问,强制要求所有内存访问都是对齐的。
3. 异常风险:在启用了未对齐访问陷阱的情况下,非对齐访问会触发Usage Fault异常。
3 进行对齐和非对齐访问的实验
3.1 关于CCR.UNALIGN_TRP
3.2 实验代码
以下是一个演示如何检测和处理非对齐访问的代码示例:
#include <stdint.h>
#include <string.h>
volatile uint32_t data[2] = {0x12345678, 0x9abcdef0};
/*!
* [url=home.php?mod=space&uid=247401]@brief[/url] Main program
*
* @param None
*
* @retval None
*/
int main(void)
{
volatile uint32_t *ptr;
// Point to an unaligned address (address of data[0] + 1 byte)
ptr = (uint32_t *)((uint8_t *)&data[0] + 1);
// address
*ptr = 0xDEADBEEF;
}
3.3 实验过程
1. 默认情况下(`CCR.UNALIGN_TRP` = 0):
- 处理器尝试直接执行未对齐的内存访问操作。
- 代码运行至`*ptr = 0xDEADBEEF;`时数组`data`的内容会发生变化。
2. 启用未对齐访问陷阱(`CCR.UNALIGN_TRP` = 1):
- 设置`CCR.UNALIGN_TRP`位为1:
- 处理器在执行未对齐的内存访问操作时会检测到违规行为,并触发Usage Fault异常,进入硬件错误中断。
3.4 实验结果
- 当`CCR.UNALIGN_TRP` = 0时,未对齐访问不会触发异常,处理器直接执行未对齐的内存操作。
- 当`CCR.UNALIGN_TRP` = 1时,未对齐访问会触发Usage Fault异常,处理器进入硬件错误中断。
3.5 进入硬件错误中断的原因
ARM Cortex-M4内核对未对齐访问的处理机制如下:
1. 未对齐访问的定义:
- 对于32位数据类型(如`uint32_t`),地址需要是4的倍数。比如,`0x20000000`和`0x20000004`是对齐的地址,而`0x20000001`、`0x20000002`和`0x20000003`是未对齐的地址。
2. 启用未对齐访问陷阱:
- 当`CCR.UNALIGN_TRP`位设置为1时,处理器会对未对齐访问进行额外检查。
- 如果检测到未对齐访问,处理器会触发Usage Fault异常并进入硬件错误中断。
在例子中:
- `ptr`被设置为未对齐地址(`data[0]`的地址加1),即`0x20000051`。
- 当执行`*ptr = 0xDEADBEEF;`时,处理器试图将0xDEADBEEF写入未对齐地址`0x20000051`。
反汇编代码
0x080009F8 6020 STR r0,[r4,#0x00]
- 在这条指令中,`r0`包含数据`0xDEADBEEF`,`r4`包含地址`0x20000051`。
- `STR r0, [r4, #0]`将`r0`中的数据存储到`r4`指向的地址`0x20000051`。
结果分析
- 当`CCR.UNALIGN_TRP` = 0(默认情况),处理器尝试执行该未对齐访问,并导致`data`数组内容变化。
- 当`CCR.UNALIGN_TRP` = 1,处理器检测到未对齐访问,触发Usage Fault异常,并进入硬件错误中断。
因此,在设置`CCR.UNALIGN_TRP`位为1的情况下,处理器在执行未对齐的内存存储操作时检测到违规行为,从而触发了硬件错误中断。
4 对齐访问 vs 非对齐访问
在网上的公开资料称非对齐访问会造成实际的程序运行开销较大。
处理器在执行未对齐访问时需要进行以下额外操作:
1. 多次内存访问:
- 从多个内存位置读取和写入数据。
2. 数据重组:
- 需要对读取的数据进行合并和移位操作。
3. 写入多个位置:
- 写回操作涉及多个内存位置。
这些额外的操作增加了指令执行的复杂性和时间开销。
5 性能开销实验
5.1 示例代码
我们可以通过实际的实验来验证未对齐访问的性能开销。以下是一个简单的示例代码,用于比较对齐和未对齐访问的性能:
#include <stdint.h>
#include <stdio.h>
#include "system_m4_dwtmeasure.h"
volatile uint32_t data_aligned[2] = {0x12345678, 0x9abcdef0};
volatile uint8_t data_unaligned[8] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
int main(void)
{
// 对齐访问测试
GET_DWT_CYCLE_COUNT(DWTCycleCount[0], \
for (uint32_t i = 0; i < 1000000; i++) { \
data_aligned[0] = 0xDEADBEEF; \
});
printf("Aligned access time: %d cycles\n", DWTCycleCount[0]);
// 非对齐访问测试
uint32_t *ptr_unaligned = (uint32_t *)(data_unaligned + 1);
GET_DWT_CYCLE_COUNT(DWTCycleCount[1], \
for (uint32_t i = 0; i < 1000000; i++) { \
*ptr_unaligned = 0xDEADBEEF; \
});
printf("Unaligned access time: %d cycles\n", DWTCycleCount[1]);
}
5.2 测试结果
对齐访问使用的Cycle是:10000024。
非对齐访问使用的Cycle是:12000016。
即:非对齐访问实际上的时间开销比对齐访问的时间要长。
6 总结
未对齐访问的性能开销主要来源于处理器需要执行多次内存访问、数据重组和写入多个内存位置等额外操作。这些操作增加了指令执行的复杂性和时间,从而降低了系统性能。因此,在嵌入式系统开发中,应尽量使用对齐的内存访问方式,以优化性能和稳定性。
这里是代码:
APM32F4_内存对齐访问你知道_code.zip
(768.88 KB)
该代码基于APM32F407_TINY板。
7 参考文档
本文参考以下文档:
1. ARM Cortex-M4 Technical Reference Manual:
- [ARM Cortex-M4 Technical Reference Manual](https://developer.arm.com/documentation/100166/0001/)
2. ARMv7-M Architecture Reference Manual:
- [ARMv7-M Architecture Reference Manual](https://developer.arm.com/documentation/ddi0403/d/Application-Level-Architecture/ARM-Architecture-Memory-Model/Alignment-support/Alignment-behavior?lang=en)
3. [Data alignment: Straighten up and fly right - IBM Developer](https://developer.ibm.com/articles/pa-dalign/)
|