在 Android (ARM) 上比较和交换

Compare and Exchange on Android (ARM)

下面的代码是 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 中设置的监视器,如果不相等,这如何安全?

还有为什么我们需要额外的 it eq 用于 thumb2?

Does the strexeq clear the monitor set in the ldrex even if the condition is not equal?

没有。它也不需要 - 这是 cmpxchg 的 "cmp" 部分 - 如果加载的值不是预期的值,那么 teq 给出 ne 条件,没有任何反应,我们由于 mov %1, #0、return 而脱离循环,每个人都忘记了整件事。

如果加载的值正确的,那么我们尝试条件strex来交换它。

ldrex所做的只是设置一个标志(独占监视器)来表示"nobody has touched this area of memory since my ldrex"。如果有人随后写入该区域,则该标志被清除。当且仅当它发现标志仍然设置时,strex 才会成功。如果它发现标志被清除,这意味着加载的值可能在内存中发生了变化,这违反了操作的原子性,因此存储失败并且没有更新发生。在这种情况下,我们必须直接回到开头并从头开始重试 - 最终,我们将不间断地完成整个序列,此时它似乎是一个原子更新。

两种情况下都不用担心独占监听状态;根据定义,任何后来的独占代码都将以 ldrex 开头,并且会在该点适当地初始化监视器。

Also why do we need extra it eq for thumb2?

因为 Thumb 没有条件执行(分支除外),因此没有位可以在指令编码中嵌入条件代码。 Thumb-2 引入了 the it instruction 作为一种方法,通过全局 ITSTATE 将最多 4 条后续指令组成一个块,这些指令基于特定条件(或其相反条件)。虽然 一些 汇编程序足够聪明,可以在为 Thumb-2 汇编 ARM 代码时自动生成适当的 it 块,但在可移植代码中不一定可以依赖它。

一个行为良好的汇编器在为 ARM 进行汇编时应该忽略 it(但如果它不符合以下指令中的条件,仍然会出错),但它可能为了好处而在此处进行了预处理器处理愚蠢的编译器通过计算换行符来猜测内联 asm 块的长度。