[APM32F4] APM32F4_内存对齐访问你知道吗?

[复制链接]
4362|38
 楼主| kai迪皮 发表于 2024-7-31 17:04 | 显示全部楼层 |阅读模式
在嵌入式系统开发中,内存访问的对齐性是一个重要的概念。本文将给各位简单分析字节对齐访问与非对齐访问的差异,并介绍在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

image-20240731105215676.png

3.2 实验代码

以下是一个演示如何检测和处理非对齐访问的代码示例:

  1. #include <stdint.h>
  2. #include <string.h>

  3. volatile uint32_t data[2] = {0x12345678, 0x9abcdef0};

  4. /*!
  5. * [url=home.php?mod=space&uid=247401]@brief[/url]       Main program
  6. *
  7. * @param       None
  8. *
  9. * @retval      None
  10. */
  11. int main(void)
  12. {
  13.     volatile uint32_t *ptr;
  14.    
  15.     // Point to an unaligned address (address of data[0] + 1 byte)
  16.     ptr = (uint32_t *)((uint8_t *)&data[0] + 1);
  17.    
  18.     //  address
  19.     *ptr = 0xDEADBEEF;
  20. }

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时,未对齐访问不会触发异常,处理器直接执行未对齐的内存操作。

image-20240731112522518.png

- 当`CCR.UNALIGN_TRP` = 1时,未对齐访问会触发Usage Fault异常,处理器进入硬件错误中断。

image-20240731112139972.png

3.5 进入硬件错误中断的原因

image-20240731104821025.png

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`。

image-20240731112306124.png

反汇编代码

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 示例代码

我们可以通过实际的实验来验证未对齐访问的性能开销。以下是一个简单的示例代码,用于比较对齐和未对齐访问的性能:

  1. #include <stdint.h>
  2. #include <stdio.h>
  3. #include "system_m4_dwtmeasure.h"

  4. volatile uint32_t data_aligned[2] = {0x12345678, 0x9abcdef0};
  5. volatile uint8_t data_unaligned[8] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};

  6. int main(void)
  7. {
  8.     // 对齐访问测试
  9.     GET_DWT_CYCLE_COUNT(DWTCycleCount[0], \
  10.        for (uint32_t i = 0; i < 1000000; i++) { \
  11.             data_aligned[0] = 0xDEADBEEF; \
  12.         });
  13.     printf("Aligned access time: %d cycles\n", DWTCycleCount[0]);

  14.     // 非对齐访问测试
  15.     uint32_t *ptr_unaligned = (uint32_t *)(data_unaligned + 1);
  16.     GET_DWT_CYCLE_COUNT(DWTCycleCount[1], \
  17.     for (uint32_t i = 0; i < 1000000; i++) { \
  18.         *ptr_unaligned = 0xDEADBEEF; \
  19.     });
  20.     printf("Unaligned access time: %d cycles\n", DWTCycleCount[1]);
  21. }

5.2 测试结果

image-20240731113644636.png

对齐访问使用的Cycle是:10000024。

非对齐访问使用的Cycle是:12000016。

即:非对齐访问实际上的时间开销比对齐访问的时间要长。

6 总结

未对齐访问的性能开销主要来源于处理器需要执行多次内存访问、数据重组和写入多个内存位置等额外操作。这些操作增加了指令执行的复杂性和时间,从而降低了系统性能。因此,在嵌入式系统开发中,应尽量使用对齐的内存访问方式,以优化性能和稳定性。

这里是代码: APM32F4_内存对齐访问你知道_code.zip (768.88 KB, 下载次数: 1) 该代码基于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/)


sdlls 发表于 2024-8-4 09:23 | 显示全部楼层
在嵌入式系统开发中,内存访问的对齐性确实是一个非常重要的概念。内存对齐是指数据在内存中的存储地址与其大小相匹配。
wilhelmina2 发表于 2024-8-4 10:28 | 显示全部楼层
对齐的数据访问通常比未对齐的数据访问更快,因为对齐可以避免额外的读写操作。
geraldbetty 发表于 2024-8-4 11:16 | 显示全部楼层
主要目的是为了提高内存访问的速度和效率,同时也有助于简化硬件设计,并提高软件的可移植性。
averyleigh 发表于 2024-8-4 12:14 | 显示全部楼层
不同的数据类型通常有不同的对齐要求。例如,char通常不需要对齐,而int、float等通常需要对其数据类型大小的整数倍对齐。
pl202 发表于 2024-8-4 13:50 | 显示全部楼层
不同的数据类型有不同的对齐要求。例如,在许多嵌入式系统中,int类型的数据通常要求4字节对齐,而long类型的数据则要求8字节对齐。
youtome 发表于 2024-8-4 15:30 | 显示全部楼层
结构体中的成员通常按照其数据类型的大小进行对齐。如果结构体的起始地址不是其最大成员的对齐边界,则会在起始地址和最大成员的对齐边界之间填充字节,以确保结构体的起始地址是对齐的。
lzbf 发表于 2024-8-4 17:08 | 显示全部楼层
现代处理器通常具有缓存(Cache)机制,用于暂存频繁访问的数据。缓存是以缓存行(Cache Line)为单位进行存储的,缓存行的大小通常为64字节。当数据跨越多个缓存行时,可能导致缓存行的浪费和缓存命中率降低。通过保持内存访问的缓存行对齐,可以减少这种浪费并提高缓存的利用率。
bestwell 发表于 2024-8-5 09:53 | 显示全部楼层
许多现代处理器在访问内存时,对对齐的数据访问速度更快。这是因为处理器内部的硬件电路在处理对齐的数据时更为高效。对齐的数据访问可以减少内存访问次数,提高数据传输效率。
backlugin 发表于 2024-8-5 11:47 | 显示全部楼层
不同的硬件平台对内存访问有不同的要求,通过内存对齐,可以提高软件在不同平台上的兼容性。
deliahouse887 发表于 2024-8-5 13:45 | 显示全部楼层
对齐的内存访问使得硬件设计更加简单,降低了电路的复杂性和成本。
minzisc 发表于 2024-8-5 15:46 | 显示全部楼层
大多数现代处理器在访问对齐的内存地址时能够更高效地执行操作。未对齐的访问可能需要额外的时钟周期来处理,从而降低系统的性能。
elsaflower 发表于 2024-8-5 17:38 | 显示全部楼层
内存对齐是嵌入式系统开发中的一个基本概念,了解和正确应用内存对齐原则对于编写高效和可靠的嵌入式软件至关重要。
macpherson 发表于 2024-8-5 19:29 | 显示全部楼层
一个4字节的数据项应该从一个能被4整除的地址开始存储。内存对齐对于提高内存访问效率和避免硬件异常至关重要。
mattlincoln 发表于 2024-8-5 21:16 | 显示全部楼层
在定义数据结构时,可以通过填充(padding)来确保字段的对齐。例如,在一个结构体中,可以在一个字段后面添加额外的字节,以确保下一个字段的正确对齐。
updownq 发表于 2024-8-6 10:03 | 显示全部楼层
C语言等编程语言提供了一定的灵活性,允许程序员通过特定的语法或编译指令来手动控制内存对齐。
sanfuzi 发表于 2024-8-6 12:26 | 显示全部楼层
数组中的元素通常按照其数据类型的大小进行对齐。如果数组的起始地址不是其第一个元素的对齐边界,则会在起始地址和第一个元素的对齐边界之间填充字节,以确保数组的起始地址是对齐的。
vivilyly 发表于 2024-8-6 14:00 | 显示全部楼层
许多编译器提供了选项来控制内存对齐方式。例如,GCC编译器提供了#pragma pack指令来控制数据结构的对齐方式。
wilhelmina2 发表于 2024-8-6 15:39 | 显示全部楼层
可以通过编译器选项或关键字(如 __attribute__((aligned(N))))来强制数据对齐到特定边界。
rosemoore 发表于 2024-8-6 17:16 | 显示全部楼层
许多处理器在访问内存时有对齐要求。例如,某些处理器可能需要数据以2字节、4字节或8字节对齐的方式存放。如果数据没有正确对齐,处理器可能需要多次访问才能读取或写入一个值,这会降低程序的性能。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

45

主题

300

帖子

11

粉丝
快速回复 在线客服 返回列表 返回顶部