ARM 架构的基本__asm__ __volatile__ ()
功能和意义是什么?"memory"
4 回答
asm volatile("" ::: "memory");
创建编译器级别的内存屏障,强制优化器不对跨屏障的内存访问重新排序。
例如,如果您需要以特定顺序访问某个地址(可能是因为该内存区域实际上由不同的设备而不是内存支持),您需要能够将这一点告诉编译器,否则它可能只会优化您的步骤为了效率。
假设在这种情况下,您必须在地址中增加一个值,读取一些内容并在相邻地址中增加另一个值。
int c(int *d, int *e) {
int r;
d[0] += 1;
r = e[0];
d[1] += 1;
return r;
}
问题是编译器(gcc
在这种情况下)可以重新安排您的内存访问以获得更好的性能,如果您要求它(-O
)。可能会导致一系列指令,如下所示:
00000000 <c>:
0: 4603 mov r3, r0
2: c805 ldmia r0, {r0, r2}
4: 3001 adds r0, #1
6: 3201 adds r2, #1
8: 6018 str r0, [r3, #0]
a: 6808 ldr r0, [r1, #0]
c: 605a str r2, [r3, #4]
e: 4770 bx lr
d[0]
上面的和值d[1]
同时加载。假设这是您想要避免的事情,那么您需要告诉编译器不要重新排序内存访问,那就是使用asm volatile("" ::: "memory")
.
int c(int *d, int *e) {
int r;
d[0] += 1;
r = e[0];
asm volatile("" ::: "memory");
d[1] += 1;
return r;
}
所以你会得到你想要的指令序列:
00000000 <c>:
0: 6802 ldr r2, [r0, #0]
2: 4603 mov r3, r0
4: 3201 adds r2, #1
6: 6002 str r2, [r0, #0]
8: 6808 ldr r0, [r1, #0]
a: 685a ldr r2, [r3, #4]
c: 3201 adds r2, #1
e: 605a str r2, [r3, #4]
10: 4770 bx lr
12: bf00 nop
应该注意的是,这只是编译时内存屏障,以避免编译器重新排序内存访问,因为它没有额外的硬件级指令来刷新内存或等待加载或存储完成。如果 CPU 具有架构功能并且内存地址在normal
类型上而不是strongly ordered
或device
( ref ) 上,它们仍然可以重新排序内存访问。
这个序列是编译器内存访问调度屏障,如 Udo 引用的文章中所述。这是 GCC 特有的——其他编译器有其他描述它们的方式,其中一些具有更明确(和不那么深奥)的语句。
__asm__
是允许在 C 代码中嵌套输入汇编语言语句的 gcc 扩展 - 此处使用它的特性是能够指定阻止编译器执行某些类型的优化的副作用(在这种情况下可能最终生成不正确的代码)。
__volatile__
需要确保asm语句本身不会被任何其他 volatile 访问重新排序(C 语言中的保证)。
memory
是 GCC 的一条指令,它(有点)说内联 asm 序列对全局内存有副作用,因此不仅需要考虑对局部变量的影响。
这里解释一下含义:
http://en.wikipedia.org/wiki/Memory_ordering
基本上它意味着汇编代码将在您期望的地方执行。它告诉编译器不要对其周围的指令重新排序。也就是这段代码之前执行的代码,之后执行的代码。
static inline unsigned long arch_local_irq_save(void)
{
unsigned long flags;
asm volatile(
" mrs %0, cpsr @ arch_local_irq_save\n"
" cpsid i" //disabled irq
: "=r" (flags) : : "memory", "cc");
return flags;
}