ARM嵌入式系统为何要做内存对齐

一起学嵌入式 2024-06-17 07:48

扫描关注一起学嵌入式,一起学习,一起成长

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

CPU体系结构和MMU的要求

  • 目前有一些RISC指令集的CPU不支持非对齐的内存变量访问操作,比如 MIPS/PowerPC/某些DSP等等,如果发生非对齐的内存访问,会产生unaligned exception 异常。
  • ARM指令集是从ARMv6(ARM11)开始支持非对齐内存访问的,以前老一点的ARM9的CPU也是不支持非对齐访问的。ARM指令集支持的部分特性迭代如下:

  • 尽管现代的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是否支持非对齐内存访问。

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

  • 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访问规则

    • 只有Normal Memory是支持非对齐内存访问的
    • Strongly-ordered 和 Device Memory不支持非对齐内存访问

对原子操作的影响

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

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

ARM NEON的要求

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

  • 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的长度一次读写内存中的映射地址。

  • 在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手册进行调整、对齐、优化.

  • 下图是一个例子关于未做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 

long timediff(clock_t t1, clock_t t2) {
    long elapsed;
    elapsed = ((double)t2 - t1) / CLOCKS_PER_SEC * 1000;
    return elapsed;
}

int main(int argc, char *argv[])
#*******
{

    int array_size=atoi(argv[1]);
    int repeat_times = 1000000000;
    long array[array_size];
    for(int i=0; i        array[i] = 0;
    }
    int j=0;
    int k=0;
    int c=0;
    clock_t start=clock();
    while(j++        if(k==array_size){
            k=0;
        }
        c = array[k++];
    }
    clock_t end =clock();
    printf('%lu\n', timediff(start,end));
    return 0;
}

  • 没有对齐到同一个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 
 
using namespace std;
 
static const int64_t MAX_THREAD_NUM = 128;
 
static int64_t n          = 0;
static int64_t loop_count = 0;
 
#pragma pack (1)
struct data
{
  int32_t pad[15];
  int64_t v;
};
#pragma pack ()
 
static data value __attribute__((aligned(64)));
static int64_t counter[MAX_THREAD_NUM];
 
void worker(int *cnt)
{
  for (int64_t i = 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  \n', argv[0]);
      exit(1);
  }
 
  /* Parse argument */
  n          = min(atol(argv[1]), MAX_THREAD_NUM);
  loop_count = atol(argv[2]); /* Don't bother with format checking */
 
  /* Start the threads */
  for (int64_t i = 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\n', sizeof(value));
  printf('data addr: %lX\n', (unsigned long)&value.v);
  printf('final: %016lX\n', value.v);
 
  return 0;
}
原文:https://blog.csdn.net/zhou_chenz/article/details/102610992

文章来源于网络,版权归原作者所有,如有侵权,请联系删除。



关注【一起学嵌入式】,回复加群进技术交流群。



觉得文章不错,点击“分享”、“”、“在看” 呗!

一起学嵌入式 公众号【一起学嵌入式】,RTOS、Linux编程、C/C++,以及经验分享、行业资讯、物联网等技术知
评论
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 69浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 108浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 82浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 64浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 100浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 97浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 118浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 34浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 104浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦