static inline unsigned long long __cmpxchg64(unsigned long long *ptr,unsigned long long old,unsigned long long new)
{
unsigned long long oldval;
unsigned long res;
prefetchw(ptr);
__asm__ __volatile__(
"1: ldrexd %1, %H1, [%3]\n"
" teq %1, %4\n"
" teqeq %H1, %H4\n"
" bne 2f\n"
" strexd %0, %5, %H5, [%3]\n"
" teq %0, #0\n"
" bne 1b\n"
"2:"
: "=&r" (res), "=&r" (oldval), "+Qo" (*ptr)
: "r" (ptr), "r" (old), "r" (new)
: "cc");
return oldval;
}
我在 gnu 手册(扩展扩展汇编)中发现“%H1”中的“H”意味着“向可偏移表内存引用添加 8 个字节”。
但是我认为如果我想将双字长数据加载到oldval(一个long long值),应该在'%1'(oldval的低32位)上添加4个字节作为oldval的高32位。那么我的错误是什么?
我在gnu手册(扩展扩展asm)中发现“%H1”中的“H”意味着“向偏移表内存引用添加8个字节”。
模板修饰符表仅适用于 x86。不适用于ARM。
不幸的是,ARM 的模板修饰符没有记录在 GCC 手册中(尽管它们适用于 AArch64),但它们在 armclang 手册中定义,并且据我所知,GCC 符合这些定义。所以这里的
H
模板修饰符的正确含义是:
操作数必须使用r约束,并且必须是64位整数或浮点类型。操作数被打印为保存一半值的最高编号寄存器。
现在这是有道理的。内联汇编的操作数 1 是
oldval
,其类型为 unsigned long long
,64 位,因此编译器将为其分配两个连续的 32 位通用寄存器。假设它们是 r4
和 r5
,如 这个编译输出 所示。然后%1
将扩展为r4
,%H1
将扩展为r5
,这正是ldrexd
指令所需要的。同样,%4, %H4
扩展为r2, r3
,%5, %H5
扩展为fp, ip
,它们是r11, r12
的替代名称。
frant 的答案解释了比较交换应该做什么。 (拼写
cmpxchg
可能来自 x86 比较交换指令的助记符。)如果您现在通读代码,您应该会发现它正是这样做的。如果 teq; teqeq; bne
和 ldrexd
不相等,则 strexd
和 old
之间的 *ptr
将中止存储。如果独占存储失败,teq; bne
之后的strexd
将导致重试,如果存在对*ptr
的干预访问(由另一个核心、中断处理程序等),就会发生这种情况。这就是确保原子性的方式。