【问题标题】:What is non-aligned access? (ARM/Keil)什么是非对齐访问? (ARM/基尔)
【发布时间】:2014-01-04 20:32:27
【问题描述】:

我正在使用 Keil 为 ARM 7 编写程序集。

我有以下运行时错误:

Non-aligned Access: ARM Instruction at 000000F8H, Memory Access at 7F7F7F7FH
Data Abort: ARM Instruction at 000000F8H, Memory Access at 7F7F7F7FH

这并没有真正帮助我,因为我不知道“非对齐访问”是什么,(除了显而易见的,但我并不真正理解它的含义)我试图访问(存储)0x7F7F7F7F,有什么问题?

搜索我只发现了几个类似的问题,都使用 C,并且通过某种方式解决了他们的代码非常具体的问题,并且与这个问题无关。

我在做:

LDR R0, =0x7F7F7F7F
LDR R1, LABEL
STR R1, [R0]

然后我使用不同的标签和R0 的偏移量做类似的事情,但它首先失败了。

【问题讨论】:

    标签: assembly arm keil arm7


    【解决方案1】:

    问题是您用于 32 位(4 字节)内存操作的地址必须与 4 字节边界对齐。这意味着地址必须是 4 的倍数,或者如果您愿意,地址的低两位必须为零。

    在这种情况下,最接近的 4 字节对齐地址是 0x7F7F7F7C0x7F7F7F80

    同样,LDRH/STRH 需要 2 字节对齐,而 LDRB/STRB 可以在任何位置操作(1 字节对齐 == 未对齐)。

    一般来说,编译器/汇编器负责确保您的变量根据它们的大小正确对齐 - 如果您自己生成地址(根据问题),您应该只遇到这种情况。

    【讨论】:

    • 哦。当然。如果您在 Rx 未对齐的情况下执行 LDR Rd [Rx] 会发生什么?它是向上取整、向下取整还是抛出错误?
    • @OllieFord 取决于SCTLR 中对齐标志的设置,您将得到一个异常,或者来自向下舍入地址的数据旋转(基本上是@987654330 的结果@ 没有最终掩码到 8 位)-我一直在使用没有此限制的 v7 和 v8 东西,所以如果这不太正确,请相信 ARM ARM 超过我 ;)
    • 感谢您的帮助。设法解决它,使用不同的地址。我仍然收到数据中止错误吗? (与上面的代码相同,但R0 := 0x0F000000
    • @OllieFord 实际上有什么映射到那个地址吗?您的 SoC 手册应该告诉您内存映射,即哪些地址后面实际上有 RAM 或 I/O 端口。
    • 混蛋。知道了谢谢。这将比我想象的更难。本来是一个“聪明”的技巧来加快代码速度,但实际上当我让它工作时,它可能会减慢它的速度!
    【解决方案2】:

    当我们谈论几十年时,我们谈论的是 70 年代或 80 年代等。我们不谈论 62 年代或 94 年代。 70、80、90 是十位对齐的数字。 10 的 1 次方。世纪是 10 的 2 次方,100 秒。 1900 年代、1400 年代等。或者将其视为数字末尾的许多零。

    寻址字节也是如此。在上面的例子中,一年是最小的单位,当我们谈论内存地址时,一个字节是最小的单位,是的,位更小,但我们不单独寻址位。我们寻址字节。与上述年份一样,任何单独的年份都可以谈论 1971、1436 等。地址 0x1234、0x21 也是如此。但是当我们想开始使用 8 位寻址方案进行 16 位访问时,就像谈论几十年,2 的 1 次幂,所以 2 0,2,4,6,8 的单位是对齐地址,用于访问 2 的幂1 个字节数(一个 16 位数字,2 个字节)。如果我们想要进行 32 位访问,即 4 字节访问或 2 的 2 次幂,就像上面的世纪一样,我们需要在地址 0x0、0x4、0x8 等结尾处有两个零(4 是 100 二进制,一个 8 是 1000 二进制 0xC 是 1100 二进制,最后两个零)。依此类推,64 位访问是 8 字节或 2 的 3 字节数的幂,因此对齐的地址末尾有 3 个零,末尾没有 3 个零的任何内容都是未对齐的。

    您上面的 32 位访问使用以 0x7F 结尾的地址,二进制是 01111111,最后两位是 11,不是零,因此不是对齐访问。

    当您进行非对齐访问时,arm、mips 或任何其他计算机会做什么,有些会捕获异常并且不允许您这样做,有些会以您意想不到的方式调整数据,有些只是让您这样做。有些像较新的 arm 可以在运行时针对不同的响应进行配置,较新的 arm 可以让您拥有类似 x86 的体验。

    不幸的是,有太多的 x86 和许多从 x86 中产生的不良编程习惯没有更多地表达惩罚,x86 使用未对齐访问肯定会受到惩罚,惩罚就是性能。 arm 和 mips 和其他人更喜欢杀死你的程序,但例外是一种非常严厉的惩罚,但却是一个很好的惩罚,因为它教会你不要那样做。

    如果你在那个地址有东西,那么你应该使用较小的传输大小(四个单独的字节传输或两个字节和一个半字)来访问它,如果你真的需要它作为一个 32 位数32 位数字。

    【讨论】:

    • 实际上,在现代英特尔架构上,性能方面并没有什么大不了的。在 i7 上基本相同。
    • 所有架构都可以,内存总线不是无限宽的,为了消除惩罚,会有数十亿个引脚。惩罚也会影响缓存等,实际上缓存 CAN(不是在每次访问时)放大了不进行对齐访问的惩罚。没有任何架构可以避免这种情况。
    • 我要做的是根据注册内容分支到不同的位置。加载值Rx 指向并跳转到该位置指定的地址。然而,您的 cmets 让我重新思考,我意识到这种方法无论如何都不是最理想的,这要归功于必要的LDR。相反,我现在使用先前的指令设置标志,然后是 BEQBNE - 显然,无论是第二个,都会受到 1 个 instr 的惩罚,所以我正在按照顺序来决定哪个效果更好。
    • 你绝对不能跳转到一个未对齐的地址,所有指令都是对齐的,无论是 16 位 thumb 还是 32 位 arm 指令。
    • 跳转到您使用 bx、bx r2 或任何寄存器的寄存器地址。
    【解决方案3】:

    正如您所注意到的,某些平台不允许对内存进行非对齐访问。读取和写入预计在 N 字节边界上对齐。我不知道你的平台,但假设我们需要 4 字节对齐。

    你有一个地址 0x7F7F7F7F。 0x7F7F7F7F % 4 == 3,即,您有余数,并且此地址未在四字节边界上对齐。许多平台上的非对齐访问会比对齐访问慢(您可能想看看 C 是如何填充结构的),但有些架构根本不允许这样做。

    因此,如果您要在固定地址向下传输数据,请确保所述地址从 N 字节边界开始(对于 ARM 7 上的 LDR,N 为 4)。您的编译器将为您合成对齐的访问权限,但如果您对地址进行硬编码(显然),则不会。

    从一些快速阅读来看,似乎在 ARM7 上实际上允许未对齐的访问 ,但有一个警告。从下面的链接:

    此外,未对齐访问仅允许对标记为正常内存类型的区域进行,并且必须通过设置系统控制协处理器中的 SCTLR.A 位来启用未对齐访问支持。在不允许的情况下尝试执行未对齐的访问将导致对齐错误(数据中止)。

    延伸阅读:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka15414.html

    【讨论】:

    【解决方案4】:

    以上所有内容都是正确的,但我不确定您展示的代码是否能满足您的需求: 我猜您正确定义了字对齐的 LABEL,那么为什么要加载十六进制地址作为您要存储的位置。 也许您只想将值 x'7F7F7F7F' 存储在内存位置 LABEL 中。 在这种情况下,你必须写 STR R0, [R1]

    【讨论】:

      【解决方案5】:

      检查数据类型。

      例如,假设您已将数组声明为 unsigned int V1[25][25]; 你有extern它作为extern int (*V1)[22];

      假设您正在使用返回 this 的函数,

      unsigned long func()
         {`unsigned long k;
            return (V1[0][0]+k);   //you will get an error.'
      
         }
      

      为避免这种情况,请在 extern 中使用与 extern unsigned int V1[25][25]. 相同的数据类型

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-22
        • 2017-10-02
        • 2016-11-26
        • 2012-09-11
        • 1970-01-01
        • 2016-02-14
        • 2011-07-04
        相关资源
        最近更新 更多