这个比较交换函数中的内联汇编是如何工作的? (ARM 上的 %H 修饰符)

问题描述 投票:0回答:1
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位。那么我的错误是什么?

gcc arm inline-assembly compare-and-swap
1个回答
1
投票

我在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
的干预访问(由另一个核心、中断处理程序等),就会发生这种情况。这就是确保原子性的方式。

© www.soinside.com 2019 - 2024. All rights reserved.