0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

ARM嵌入式系统中内存对齐的重要性

嵌入式与Linux那些事 来源:嵌入式与Linux那些事 2024-11-11 17:17 次阅读

嵌入式系统软件开发,经常在代码中看到各种各样的对齐,很多时候我们都是知其然不知其所以然,知道要做好各种对齐,但是不明白为什么要对齐,不对齐会有哪些后果,这篇文章大概总结了内存对齐的理由。

CPU体系结构和MMU的要求

目前有一些RISC指令集的CPU不支持非对齐的内存变量访问操作,比如 MIPS/PowerPC/某些DSP等等,如果发生非对齐的内存访问,会产生unaligned exception 异常。

ARM指令集是从ARMv6(ARM11)开始支持非对齐内存访问的,以前老一点的ARM9的CPU也是不支持非对齐访问的。ARM指令集支持的部分特性迭代如下:

1a5ba9a2-905d-11ef-a511-92fbcf53809c.png

尽管现代的ARMv7 ARMv8 指令集的Cortex-AXX系列CPU都支持非对齐内存访问,但是考虑到如下图所示现代SOC芯片里面多种异构CPU协调工作的情况,主CPU用于跑Linux/Android操作系统的ARM64可以支持非对齐内存访问,但是SOC里面还有其它不知道体系结构和版本的协CPU(可能是MIPS, ARM7,Cortex-R/M系列, 甚至51单片机核),这些协CPU都和主ARM64主CPU共享物理内存的不同地址段,并且有自己的固件程序在内存上运行,所以在划分地址空间的时候还是要注意内存对齐的问题,尤其是考虑到这些协CPU可能不支持非对齐访问,同样在编写协CPU固件程序的时候,也要清晰认识到该CPU是否支持非对齐内存访问。

1a720602-905d-11ef-a511-92fbcf53809c.png

image.png

同样在ARM的MMU虚拟地址管理中,也有内存地址对齐的要求,下图是ARM的MMU的工作原理和多级页表(Translation Tables)的索引关系图

1a967578-905d-11ef-a511-92fbcf53809c.png

1aadc93a-905d-11ef-a511-92fbcf53809c.png

ARM体系架构的MMU要求

arm 32位体系结构要求L1第一级页表基地址(The L1 Translation Table Base Addr)对齐到16KB的地址边界,L2第二级页表地址(The L2 Translation Table Add)对齐到1KB的地址边界。

ARM 64位体系结构要求虚拟地址的第21-28位VA[28:21]对齐到64 KB granule, 第16到20位VA[20:16]对齐到4 KB granule。

ARM 的Memory ordering特性中的不同Memory types对非对齐内存访问的支持的要求是不同的。下图是ARM Memory ordering特性中三种不同的Memory types访问规则

1ad18104-905d-11ef-a511-92fbcf53809c.png

只有Normal Memory是支持非对齐内存访问的

Strongly-ordered 和 Device Memory不支持非对齐内存访问

对原子操作的影响

尽管现代的ARMv7 ARMv8 指令集的ARM CPU支持非对齐内存访问,但是非对齐内存访问是无法保证操作的原子性。下图分别是一个变量在内存对齐和非对齐的时候的内存布局:

1afc36c4-905d-11ef-a511-92fbcf53809c.png

1b1b2494-905d-11ef-a511-92fbcf53809c.png

内存对齐的变量访问,使用单个通用的CPU寄存器暂存,一个内存对齐的变量的读写操作能保证是单次原子操作.

非对齐的变量的内存访问是非原子操作,他们通常情况下访问一个非对齐的内存中的变量需要2次分别的对内存进行访问,因而不能保证原子性,一旦发生2次分别内存访问,2次分别的访问中间就有可能被异步事件打断,造成变量改变,因而不能保证原子性。

ARM NEON的要求

现代ARM CPU一般都有一个NEON的协处理器,一般用在浮点计算中用来做SIMD并行矢量加速计算。下图是NEON SIMD并行矢量计算的基本原理图:

1b3c0bd2-905d-11ef-a511-92fbcf53809c.png

1b63bbb4-905d-11ef-a511-92fbcf53809c.png

NEON本身是支持非对齐内存访问的

但是NEON访问非对齐的内存一般会有2个指令周期的时间penalty

通常情况下,为了灵活应用NEON的并行计算特性,在做SIMD并行矢量加速运算时,我们要根据NEON寄存器的Lane的bits数对齐相应的变量。如果是配置成8-bits的计算,就做8-bits对齐,如果是16-bits计算,就做16-bits对齐,以此类推,NEON的并行矢量计算的lane根据spec手册,有各种灵活配置的方法。

对性能perf的影响

通常而言,尽管现代的ARM CPU已经支持非对齐内存的访问,但是ARM访问非对齐的内存地址还是会造成明显的性能下降。因为访问一个非对齐的内存,需要增加多次load/store内存变量次数,进而增加了程序运行的指令周期

才有perf工具进行性能分析,能看到非对齐内存访问的性能下降,在perf工具中有一个alignment-faults的事件,可以观察程序访问非对齐内存的事件统计

cache line 对齐

除了通常所讲的根据CPU访问内存的地址位数的内存对齐之外,在程序优化的时候,还要考虑到cache存在的情况,根据cache line的长度来对齐你的访问变量。

cache和cache line的结构原理图如下(其中图2从该文章引用自: cenalulu),cache line是cache和内存进行数据传输的最小单位,一般cache都是以cache line的长度一次读写内存中的映射地址。

1b803cbc-905d-11ef-a511-92fbcf53809c.png

1b9a2974-905d-11ef-a511-92fbcf53809c.png

在ARM 系列的CPU中,不同型号的ARM CPU的cache line长度是不一样的,因此同样是基于ARM平台的CPU,从A平台移植优化过的程序到B平台时,一定要注意不同CPU的cache line大小是否一致,是否要重新调整cache line对齐优化。下图是ARMv7几款公版CPU的cache line的资料手册,ARMv8 64位的公版CPU(A53, A57, A72, A73)目前的cache line大小都是64 bytes, 但是各家公司基于公版ARM的定制版CPU的cache line大小可能有差异,一定要参考相关TRM手册进行调整、对齐、优化.

1bb581ce-905d-11ef-a511-92fbcf53809c.png

下图是一个例子关于未做cache line对齐的情况下,进行内存读写性能抖动的例子,引用自cenalulu.测试代码如下程序的大意,对不同大小的数组进行1亿次读写操作,统计不同数组size时的读写时间。从测试的结果可以看出,当数组大小小于cache line size时,读写时间基本变化不大,当数组大小刚刚超过cache line size的时候,读写时间发生了剧烈的抖动。这是因为超过cache line 大小的数组元素可能没有提前预读到cache line中,在访问完cache line中的数组元素之后,要重新从内存读取数据,刷新cache line,因而产生了性能抖动。通过这个例子告诉我们,充分利用系统cache特性,根据cache line对齐你的数据,保证程序访问的局部数据都在一个cache line中可以提升系统性能。

#include"stdio.h"
#include
#include

longtimediff(clock_tt1,clock_tt2){
longelapsed;
elapsed=((double)t2-t1)/CLOCKS_PER_SEC*1000;
returnelapsed;
}

intmain(intargc,char*argv[])
#*******
{

intarray_size=atoi(argv[1]);
intrepeat_times=1000000000;
longarray[array_size];
for(inti=0;i

1bd12a1e-905d-11ef-a511-92fbcf53809c.png

image.jpg

没有对齐到同一个cache line中的变量,在多核SMP系统中,cross cache line操作是非原子操作,存在篡改的风险。该例子引用自kongfy)测试代码如下,程序大意是,系统cpu的cache line是64字节,一个68字节的结构体struct data, 其中前面填充60字节的pad[15]数组,最后一个8字节的变量v, 这样结构体大小超过了64字节,最后一个变量v的前后部分可定不在同一个cache line中,整个结构体没法根据cache line对齐。全局变量value.v初始值是0, 程序开多线程,对全局变量value.v进行多次~位取反操作,直觉上最后结果value.v的位结果不是全0就是全1,但是最后value.v的位结果居然是一半1一半0, 这就是由于cross cache line 操作是非原子性的,导致一个线程对value.v前半部分取反的时候,另外的线程对后半部分在另一个cache line同时取反,然后前一个线程再对另一个cache line的value.v后半部分取反,导致和直觉不一致。

#include
#include
#include
#include

usingnamespacestd;

staticconstint64_tMAX_THREAD_NUM=128;

staticint64_tn=0;
staticint64_tloop_count=0;

#pragmapack(1)
structdata
{
int32_tpad[15];
int64_tv;
};
#pragmapack()

staticdatavalue__attribute__((aligned(64)));
staticint64_tcounter[MAX_THREAD_NUM];

voidworker(int*cnt)
{
for(int64_ti=0;i< loop_count; ++i) {
    const int64_t t = value.v;
 
    if (t != 0L && t != ~0L) {
      *cnt += 1;
    }
 
    value.v = ~t;
    asm volatile("" ::: "memory");
  }
}
 
int main(int argc, char *argv[])
{
  pthread_t threads[MAX_THREAD_NUM];
 
  /* Check arguments to program*/
  if(argc != 3) {
      fprintf(stderr, "USAGE: %s 
",argv[0]);
exit(1);
}

/*Parseargument*/
n=min(atol(argv[1]),MAX_THREAD_NUM);
loop_count=atol(argv[2]);/*Don'tbotherwithformatchecking*/

/*Startthethreads*/
for(int64_ti=0L;i< n; ++i) {
    pthread_create(&threads[i], NULL, (void* (*)(void*))worker, &counter[i]);
  }
 
  int64_t count = 0L;
  for (int64_t i = 0L; i < n; ++i) {
    pthread_join(threads[i], NULL);
    count += counter[i];
  }
 
  printf("data size: %lu
", sizeof(value));
  printf("data addr: %lX
", (unsigned long)&value.v);
  printf("final: %016lX
", value.v);
 
  return 0;
}

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • ARM
    ARM
    +关注

    关注

    134

    文章

    9080

    浏览量

    367294
  • 嵌入式系统
    +关注

    关注

    41

    文章

    3586

    浏览量

    129410
  • 内存
    +关注

    关注

    8

    文章

    3016

    浏览量

    73989

原文标题:【内存管理】ARM嵌入式系统为什么要做内存对齐

文章出处:【微信号:嵌入式与Linux那些事,微信公众号:嵌入式与Linux那些事】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux内核内存回收对嵌入式系统重要性

    嵌入式系统内存回收还是比较重要的,因为这块涉及到程序运行性能。
    的头像 发表于 07-14 09:25 1698次阅读

    嵌入式重要性

    器那么复杂,但计算机系统的各种组成一样也不缺。因此,以嵌入式系统作为切入点开始学习软件技术是非常好的选择,避开不必要的复杂,把握计算机系统
    发表于 05-13 10:12

    Reset对系统稳定性有什么重要性

    嵌入式系统的应用领域越来越广泛,干扰或者恶劣环境常影响嵌入式系统运行的稳定性和可靠。Reset是维护
    发表于 03-11 07:53

    Linux对嵌入式重要性

    最近遇到很多处于迷茫的就业者,在纠结要不要从事嵌入式这个行业,主要问题在于嵌入式这个行业对求职者的专业技能要求是非常高的,但是现在嵌入式开发行业的确发展很好,很多的行业都是需要用
    发表于 10-27 07:00

    嵌入式软件的重要性

    效率的重要工作。目前,装备嵌入式软件的自动化测试,更多的还是依赖代码级别的白盒测试工具;黑盒动态测试还主要是根据不同的装备需求,研发配套的工装系统,测试效率和测试深度都有很大缺陷。主要表现在以下几点:...
    发表于 10-27 06:59

    嵌入式技术的重要性

    、汽车电子、娱乐的机器人,无不采用嵌入式技术。在通讯、网络、工控、医疗、电子等领域,嵌入式发挥着越来越重要的作用。在百度搜索“嵌入式”、“
    发表于 11-08 09:28

    ARM嵌入式系统为什么要对齐?不对齐会有哪些后果

    这里写自定义目录标题做嵌入式系统软件开发,经常在代码中看到各种各样的对齐,很多时候我们都是知其然不知其所以然,知道要做好各种对齐,但是不明白为什么要
    发表于 12-14 09:09

    使用memtester工具对嵌入式Linux内存压力进行测试

    存储器等。内存是将外存与CPU连接起来的桥梁,计算机中所有数据都需经过内存进行交互,而且所有应用程序都运行在内存。可见,内存
    发表于 12-15 06:29

    编程在嵌入式重要性

    嵌入式小白入门博客时间总是不等人,转眼间自己都已经是一名研一的学生了,回想起自己过去本科的学习,讲实话根本是无规律可循,可以说自己大学四年在编程这块都没有用心去系统的学习过,当然这也和自己是控制专业
    发表于 12-17 06:32

    虚拟内存内存申请的重要性

    内存申请接口返回的内存可以直接使用, 正确由操作系统/平台来保证,没必要判断返回结果。答案:错误在嵌入式
    发表于 12-17 06:30

    嵌入式系统内存管理方案研究

    摘要:嵌入式系统内存管理机制必须满足实时和可靠的要求。本文以开源的的操作系统RTEMS
    发表于 05-24 23:57 1154次阅读
    <b class='flag-5'>嵌入式</b><b class='flag-5'>系统</b><b class='flag-5'>内存</b>管理方案研究

    Reset对嵌入式系统稳定性的重要性分析

    嵌入式系统 的应用领域越来越广泛,干扰或者恶劣环境常影响嵌入式系统运行的 稳定性 和可靠。 Reset 是维护
    发表于 02-07 09:04 2201次阅读
    Reset对<b class='flag-5'>嵌入式</b><b class='flag-5'>系统</b>稳定性的<b class='flag-5'>重要性</b>分析

    嵌入式系统内存指针操作

    到处理器的内存空间中。在x86系统,分为内存和I/O映射两种内存;在ARM体系
    的头像 发表于 08-27 11:33 6188次阅读

    ARM嵌入式系统

    一、常见的ARM嵌入式系统开发环境配置:1、编译器/汇编器2、指令系统模拟器3、在线仿真器或调试探测器4、目标开发板5、跟踪捕捉仪6、嵌入式
    发表于 10-20 18:20 6次下载
    <b class='flag-5'>ARM</b><b class='flag-5'>嵌入式</b><b class='flag-5'>系统</b>

    RAM测试是什么?嵌入式系统开发RAM测试的重要性

    嵌入式系统在众多关键应用中发挥着至关重要的作用,涵盖汽车和航空航天工业到医疗设备和工业控制系统领域。随着嵌入式
    发表于 07-28 11:11 2368次阅读