下面的代码是android上的比较和交换的ARM实现:
__ATOMIC_INLINE__ int __bionic_cmpxchg(int32_t old_value, int32_t new_value, volatile int32_t* ptr) {
int32_t prev, status;
do {
__asm__ __volatile__ (
"ldrex %0, [%3]\n"
"mov %1, #0\n"
"teq %0, %4\n"
#ifdef __thumb2__
"it eq\n"
#endif
"strexeq %1, %5, [%3]"
: "=&r" (prev), "=&r" (status), "+m"(*ptr)
: "r" (ptr), "Ir" (old_value), "r" (new_value)
: "cc");
} while (__builtin_expect(status != 0, 0));
return prev != old_value;
}
然后,即使条件不相等,strexeq也会清除ldrex中的监视器集,如果不相同,那么这样做是否安全?
为什么我们为thumb2需要额外的it eq
呢?
答案 0 :(得分:3)
即使条件不相等,
strexeq
是否清除ldrex
中的监视器集?
没有。它也不需要 - 这是cmpxchg的“cmp”部分 - 如果加载的值不是预期值,那么teq
给出ne
条件,没有任何反应,我们就会失败循环由于mov %1, #0
,返回,并且每个人都忘记了整个事情。
如果加载的值 是正确的那么我们会尝试条件strex
进行交换。
所有ldrex
确实设置了一个标志(独占监视器),说“自ldrex
以来没有人触及过这个记忆区域”。如果有人写入该区域,则清除该标志。当且仅当它仍然设置了标志时,strex
才会成功。如果它发现标志已清除,则表示加载的值可能已在内存中更改,这违反了操作的原子性,因此存储失败并且不进行更新。在这种情况下,我们必须回到开头并从头开始重试 - 最终,我们将不间断地完成整个序列,此时它似乎是原子更新。
在任何一种情况下都无需担心独占显示器状态;根据定义,任何后来的独占代码都以ldrex
开头,这将在那时恰当地初始化监视器。
为什么我们为thumb2需要额外的
it eq
呢?
因为Thumb没有条件执行(分支除外),因此在指令编码中没有用于嵌入条件代码的位。 Thumb-2引入the it
instruction作为通过全局ITSTATE对特定条件(或其相反)进行预测的最多4个后续指令的方法。虽然某些汇编程序足够聪明,可以在为Thumb-2组装ARM代码时自动生成适当的it
块,但这并不是您在移植代码中必须依赖的东西。
一个表现良好的汇编程序在为ARM组装时应该忽略it
(但是如果它与下面的指令中的条件不匹配仍然是错误的),但它可能是为了愚蠢的利益而预处理的通过计算换行符来猜测内联asm块长度的编译器。