ARM Cortex M7 非对齐访问和 memcpy

7

我正在使用GCC为Cortex M7编译此代码:

// copy manually
void write_test_plain(uint8_t * ptr, uint32_t value)
{
    *ptr++ = (u8)(value);
    *ptr++ = (u8)(value >> 8);
    *ptr++ = (u8)(value >> 16);
    *ptr++ = (u8)(value >> 24); 
}

// copy using memcpy
void write_test_memcpy(uint8_t * ptr, uint32_t value)
{
    void *px = (void*)&value;
    memcpy(ptr, px, 4);
}

int main(void) 
{
    extern uint8_t data[];
    extern uint32_t value;

    // i added some offsets to data to
    // make sure the compiler cannot
    // assume it's aligned in memory

    write_test_plain(data + 2, value);
    __asm volatile("": : :"memory"); // just to split inlined calls
    write_test_memcpy(data + 5, value);

    ... do something with data ...
}

使用-O2编译选项,我得到了以下Thumb2汇编代码:

// write_test_plain(data + 2, value);
800031c:    2478        movs    r4, #120 ; 0x78
800031e:    2056        movs    r0, #86  ; 0x56
8000320:    2134        movs    r1, #52  ; 0x34
8000322:    2212        movs    r2, #18  ; 0x12
8000324:    759c        strb    r4, [r3, #22]
8000326:    75d8        strb    r0, [r3, #23]
8000328:    7619        strb    r1, [r3, #24]
800032a:    765a        strb    r2, [r3, #25]

// write_test_memcpy(data + 5, value);
800032c:    4ac4        ldr r2, [pc, #784]  ; (8000640 <main+0x3a0>)
800032e:    923b        str r2, [sp, #236]  ; 0xec
8000330:    983b        ldr r0, [sp, #236]  ; 0xec
8000332:    f8c3 0019   str.w   r0, [r3, #25]

有人能解释一下memcpy版本是如何工作的吗?这似乎是将32位存储内联到目标地址,但是由于data + 5肯定不对齐到4字节边界,这不是个问题吗?

这可能是由于源代码中某些未定义的行为而发生的优化吗?


1
标志是SCB.CCR.UNALIGN_TRP;请注意,_强制排序_和_设备_内存禁止不对齐的访问(无论此标志如何),这通常是外围寄存器映射的位置。 - calandoa
1
即使 SCB.CCR.UNALIGN_TRP 未设置,对强序/设备内存的非对齐访问也会触发硬故障。我在 Keil 的一篇文章中找到了这个信息。 - Robert Sexton
1
@RobertSexton:谢谢,你能提供一个链接以备将来参考吗?我不记得这个了,我知道通常情况下它不会失败,所以这取决于内存的类型,对吧? - Lou
是的,正如@calandoa所描述的那样,这取决于内存类型。 - Robert Sexton
需要注意的一点是,现代编译器会尝试发出对齐的加载/存储指令,如果它能确定源和目标都是对齐的。否则,你将得到示例中编译器发出“安全”代码的情况。有优化的内存复制函数可以自动执行此操作,其中它们在读取/写入本机机器大小之前读取/写入不对齐的字节。 对齐访问要快得多。请尽可能使其成为可能。 - Robert Sexton
显示剩余4条评论
2个回答

6

对于Cortex-M处理器,字节、半字和字的未对齐加载和存储通常是允许的,并且大多数编译器在生成代码时使用此功能,除非另有指示。如果您想防止gcc假定未对齐访问是OK的,您可以使用 -mno-unaligned-access 编译器标志。

如果指定了此标志,gcc将不再内联调用 memcpywrite_test_memcpy 将如下所示

write_test_memcpy(unsigned char*, unsigned long):
  push {lr}
  sub sp, sp, #12
  movs r2, #4
  add r3, sp, #8
  str r1, [r3, #-4]!
  mov r1, r3
  bl memcpy
  add sp, sp, #12
  ldr pc, [sp], #4

谢谢!我在ARM M7用户指南中找到了这一章节,它确实说明STR和LDR可以使用不对齐访问,但是我不理解“不对齐访问通常比对齐访问慢”的备注——手册中没有任何表明LRD和STR的周期计数可能与1和2个周期不同。你知道这是什么意思,或者我可以在哪里找到关于这些情况下周期计数的信息吗? - Lou
1
对于Cortex-M处理器,通常允许未对齐的字节、半字和字的加载和存储。但这是明显错误的。它取决于配置,并且已被强烈废弃,特别是对于堆栈上的数据。启用后性能也会大大降低。 - too honest for this site
@Olaf:谢谢,但我在ARM文档中找不到有关性能影响的信息,这些指令(LDR、STR)在手册中有其确切的周期计数,并且我没有找到任何不同周期计数的提及。 - Lou
1
LDR/STR指令明确说明必须添加内存访问周期。此外,非对齐的读写不能使用LDRD/STRD/LDRM/STRM,可能只使用部分缓存行,并且每个字需要最多3次访问。而这仅仅是如果允许非对齐访问!这通常不是真的,因为如果遵循严格的别名规则,干净的代码通常不需要执行跨对齐复制。最后:在嵌入式代码中,不应该进行大量复制。仅仅出于整体性能原因。 - too honest for this site

3

Cortex-M7,M4,M3,M33和M23支持不对齐访问。

M0和M0+不支持不对齐访问。

然而,您可以通过在配置和控制寄存器中设置位UNALIGN_TRP来禁用Cortex-M7的不对齐访问支持,此时任何不对齐访问将生成用法错误。

从编译器的角度来看,默认设置是生成的汇编代码会进行不对齐访问,除非您使用编译标志-mno-unaligned-access禁用它。


谢谢。实际上,即使允许未对齐访问,我们曾经遇到过一个问题,当读取位于内存某些部分之间的地址边界时。我没有直接参与,所以我不确定具体情况,但如果我没记错的话,32位字中的最后16位是零,除非我们强制编译器将其读取为单个字节。 - Lou
@lou 这是在ST MCU上吗?它们通常有两个SRAM bank,这种行为可能是有道理的。 - stefanct
@stefanct 抱歉许久未在 SO 上露面。很抱歉我已不记得细节,但有个问题是 32 位数值未对齐,恰好处于两个地址之间的边界上。因此 mcu 会从第一个内存模块读取前一半,其余则为零。 - Lou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接