首页 > 系统相关 >【转载】ARM嵌入式系统为什么要做内存对齐

【转载】ARM嵌入式系统为什么要做内存对齐

时间:2024-06-10 13:00:51浏览次数:9  
标签:cache CPU 嵌入式 内存 对齐 line ARM

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

CPU体系结构和MMU的要求

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

image.png

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

image.jpg
image.jpg

  • 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访问规则
    image.jpg
    • 只有Normal Memory是支持非对齐内存访问的
    • Strongly-ordered 和 Device Memory不支持非对齐内存访问

对原子操作的影响

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

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

ARM NEON的要求

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

  • 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的长度一次读写内存中的映射地址。
    image.jpg
    image.jpg
  • 在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手册进行调整、对齐、优化.
    image.jpg
  • 下图是一个例子关于未做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 <stdlib.h>
#include <sys/time.h>

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_size; i++){
        array[i] = 0;
    }
    int j=0;
    int k=0;
    int c=0;
    clock_t start=clock();
    while(j++<repeat_times){
        if(k==array_size){
            k=0;
        }
        c = array[k++];
    }
    clock_t end =clock();
    printf("%lu\n", timediff(start,end));
    return 0;
}
1234567891011121314151617181920212223242526272829303132333435

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 <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <algorithm>
 
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 <threads> <loopcount>\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

标签:cache,CPU,嵌入式,内存,对齐,line,ARM
From: https://www.cnblogs.com/dongxb/p/18240594

相关文章

  • ARM64中的ASID地址空间标识符
    1.从ARM32到ARM64从ARM32到ARM64不止将处理器从32位升级到了64位,还有许多性能的技术也得到了极大的提升,光是个头长了可不行啊!能耐也得跟着长啊!哈哈哈1.1ARM32的TLB机制如上图所示,上一讲我们讲了TLB的每一条表项都有一个bit用来表示自己是全局的(内核空间)还是本地的(用户空间)。......
  • 如何下载EarMaster Pro软件及详细安装步骤
    根据大数据调查表明软件功能和优势:灵敏的音高识别系统,准确辨认出您演唱或演奏的乐段。我们必须承认来自丹麦皇家歌曲学院的多媒体歌曲教育软件EarMasterPro以问答的交互形式,寓教于乐的视听方法,给专业和非专业歌曲人士以极大的歌曲学习帮助。很明显EarMaster提供了相当多的......
  • RDK X3(arm64) 测试国产双目摄像头Astra Pro Plus
    0.环境-亚博智能的ROSMASTER-X3标准版(双目是AstraProPlus)-RDKX31.01.RDKX31.0串口通信波特率921600root/rootmobaterm->Session->VNC   ->192.168.8.108:5900   ->runrise2.是否识别到设备2.1USB扩展版上电前:root@ubuntu:~#lsusbB......
  • 嵌入式Linux中驱动程序的基本框架
    在“嵌入式Linux中内核模块的基本框架”一文中,已经构建好了内核模块的基本框架结构,现在在该框架的基础上进一步扩展,就可以形成Linux下的字符型设备驱动基本框架,下面就详细进行讨论。在Linux系统中,设备驱动共分为三种类型,即字符型、块型和网络型。字符型设备以字节为最小操作单位,......
  • 武装突袭3服务器一键开服联机( ARMA3 )
    1、购买后登录服务器(百度莱卡云)进入控制面板后会出现正在安装的界面,安装大约5分钟(如长时间处于安装中请联系我们的客服人员)等待服务器安装完成后,查看地址下方的表是否为灰色,如果是黄色等待变为灰色,如果是灰色直接下一步开始连接游戏2、转换IP地址连接游戏需要转换服......
  • HarmonyOS ArkTS组件 | Flex 以弹性方式布局子组件的容器组件 学习记录
    HarmonyOSArkTS组件|Flex以弹性方式布局子组件的容器组件学习记录前言:最近需要用到弹性布局,记录一下。(忽略图片水印QAQ)说明:Flex组件在渲染时存在二次布局过程,因此在对性能有严格要求的场景下建议使用Column、Row代替。Flex组件主轴默认不设置时撑满父容器,Column、Row组......
  • 【机器学习】与【数据挖掘】技术下【C++】驱动的【嵌入式】智能系统优化
    目录一、嵌入式系统简介二、C++在嵌入式系统中的优势三、机器学习在嵌入式系统中的挑战四、C++实现机器学习模型的基本步骤五、实例分析:使用C++在嵌入式系统中实现手写数字识别1.数据准备2.模型训练与压缩3.模型部署六、优化与分析1.模型优化模型量化模型剪枝......
  • [干货][HarmonyOS NEXT]鸿蒙中除了这些还有哪些装饰器呢?
    @Entry:将结构体标记为页面组件,代表一个完整的页面。@Component:将结构体标记为可复用的组件。@Preview:让组件能够在开发过程中进行预览。@State:用于定义组件内部的响应式状态变量需给初始值@Prop:实现父组件到子组件的数据单向传递。可以给初始值也可以不给@Link:达成父组件......
  • 一文搞懂 ARM 64 系列: 寄存器
    ARM64中包含多种寄存器,下面介绍一些常见的寄存器。1通用寄存器ARM64包含31个64bit寄存器,记为X0~X30。每一个通用寄存器,它的低32bit都可以被访问,记为W0~W30。在这31个通用寄存器中,有2个寄存器比较特殊。X29寄存器被作为栈帧寄存器,也被称为FP(FramePointerRegister)。X3......
  • 作为嵌入式/软件开发工程师你需要知道的东西
    大型软件开发的基本素养中国科学技术大学软件开发规范软件开发规范(试行版)(ustc.edu.cn) 清华软件工程样张标题(tup.com.cn)软件工程这个概念,并将其定义为“为了经济地获得可靠的和能在实际机器上高效运行的软件,而建立和使用的健全的工程规则”(1)将系统化的、严格约......