c - 使用 cmpxchg8b 未获得 unsigned long 的预期输出

标签 c x86 atomic inline-assembly compare-and-swap

我正在尝试编写一个简单的比较和交换内联汇编代码。这是我的代码

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
static inline unsigned long
cas(volatile unsigned long* ptr, unsigned long old, unsigned long _new)
{
    unsigned long prev=0;
    asm volatile("lock cmpxchg8b %0;"
                 : "=m"(prev)
                 : "m"(*ptr),"a"(old),"c"(_new)
                 );
    return prev;
}

int main()
{

    unsigned long *a;
    unsigned long b=5,c;
    a=&b;
        c=cas(a,b,6);
    printf("%lu\n",c);
    return 0;
}

这段代码理想情况下应该打印 5,但实际打印的是 0。我的代码出了什么问题?请帮忙。

最佳答案

首先让我说“使用内联汇编是一个坏主意。”让我再说一遍“使用内联汇编是一个坏主意”。你可以写一个完整的 wiki entry关于为什么使用内联汇编是一个坏主意。请考虑使用内置函数(例如 gcc 的 __sync_bool_compare_and_swap )或像 这样的库。

如果您正在编写生产软件,那么使用内联汇编的风险几乎肯定大于任何好处。如果您出于教育目的而写作,请继续阅读。

(为了进一步说明为什么不应该使用内联汇编,请等待 Michael 或 Peter 出现并指出此代码的所有错误。这真的很难,即使对于了解这些东西的人才能正确地做到这一点。)

以下代码展示了如何使用cmpxchg8b。它很简单,但应该足以给出一个总体思路。

#include <stdio.h>

// Simple struct to break up the 8 byte value into 32bit chunks.
typedef union {
  struct {
     unsigned int lower;
     unsigned int upper;
  };
  unsigned long long int f;
} moo;

unsigned char cas(moo *ptr, moo *oldval, const moo *newval)
{
   unsigned char result;

#ifndef __GCC_ASM_FLAG_OUTPUTS__

   asm ("lock cmpxchg8b %[ptr]\n\t"
        "setz %[result]"
        : [result] "=q" (result), [ptr] "+m" (*ptr),
          "+d" (oldval->upper), "+a" (oldval->lower)
        : "c" (newval->upper), "b" (newval->lower)
        : "cc", "memory");

#else

   asm ("lock cmpxchg8b %[ptr]"
        : [result] "=@ccz" (result), [ptr] "+m" (*ptr),
          "+d" (oldval->upper), "+a" (oldval->lower)
        : "c" (newval->upper), "b" (newval->lower)
        : "memory");

#endif

   return result;
}

int main()
{
   moo oldval, newval, curval;
   unsigned char ret;

   // Will not change 'curval' since 'oldval' doesn't match.
   curval.f = -1;
   oldval.f = 0;
   newval.f = 1;

   printf("If curval(%u:%u) == oldval(%u:%u) "
          "then write newval(%u:%u)\n",
          curval.upper, curval.lower,
          oldval.upper, oldval.lower,
          newval.upper, newval.lower);

   ret = cas(&curval, &oldval, &newval);

   if (ret)
      printf("Replace succeeded: curval(%u:%u)\n",
             curval.upper, curval.lower);
   else
      printf("Replace failed because curval(%u:%u) "
             "needed to be (%u:%u) (which cas has placed in oldval).\n",
             curval.upper, curval.lower,
             oldval.upper, oldval.lower);

   printf("\n");

   // Now that 'curval' equals 'oldval', newval will get written.
   curval.lower = 1234; curval.upper = 4321;
   oldval.lower = 1234; oldval.upper = 4321;
   newval.f = 1;

   printf("If curval(%u:%u) == oldval(%u:%u) "
          "then write newval(%u:%u)\n",
          curval.upper, curval.lower,
          oldval.upper, oldval.lower,
          newval.upper, newval.lower);

   ret = cas(&curval, &oldval, &newval);

   if (ret)
      printf("Replace succeeded: curval(%u:%u)\n",
             curval.upper, curval.lower);
   else
      printf("Replace failed because curval(%u:%u) "
             "needed to be (%u:%u) (which cas has placed in oldval).\n",
             curval.upper, curval.lower,
             oldval.upper, oldval.lower);

}

几点:

  • 如果 cas 失败(因为值不匹配),函数的返回值为 0,并且您需要使用的值将在 oldval 中返回。这使得再次尝试变得简单。请注意,如果您正在运行多线程(您必须如此,否则您不会使用lock cmpxchg8b),第二次尝试也可能会失败,因为“其他”线程可能已经击败你再写一次。
  • __GCC_ASM_FLAG_OUTPUTS__ 定义可在较新版本的 gcc (6.x+) 上使用。它允许您跳过 setz 并直接使用标志。请参阅海湾合作委员会docs了解详情。

至于它是如何工作的:

当我们调用cmpxchg8b时,我们向它传递一个指向内存的指针。它将将该内存位置中的(8 字节)值与 edx:eax 中的 8 字节进行比较。如果它们匹配,则它将把 ecx:ebx 中的 8 个字节写入内存位置,并设置 zero 标志。如果它们不匹配,则当前值将在 edx:eax 中返回,并且 zero 标志将被清除。

因此,将其与代码进行比较:

   asm ("lock cmpxchg8b %[ptr]"

这里我们将指向 8 个字节的指针传递给 cmpxchg8b

        "setz %[result]"

这里我们将 cmpxchg8b 设置的 zero 标志的内容存储到 (result) 中。

        : [result] "=q" (result), [ptr] "+m" (*ptr),

指定(结果)是输出(=),并且它必须是字节寄存器(q)。另外,内存指针是一个输入+输出(+),因为我们将读取它并写入它。

          "+d" (oldval->upper), "+a"(oldval->lower)

+ 号再次表明这些值是输入+输出。这是必要的,因为如果比较失败,edx:eax 将被 ptr 中的当前值覆盖。

        : "c" (newval->upper), "b"(newval->lower)

这些值仅供输入。 cmpxchg8b 不会更改它们的值,因此我们将它们放在第二个冒号之后。

        : "cc", "memory");

由于我们要更改标志,因此需要通过“cc”通知编译器。 “内存”约束可能不是必需的,具体取决于 cas 的用途。线程 1 可能正在通知线程 2 有些内容已准备好进行处理。在这种情况下,您需要绝对确保 gcc 的寄存器中没有任何它计划稍后写入内存的值。在执行 cmpxchg8b 之前,它绝对必须将它们全部刷新到内存。

gcc docs详细描述扩展 asm 语句的工作原理。如果本解释的部分内容仍然不清楚,阅读一些内容可能会有所帮助。

顺便说一句,以防我忘记提及,编写内联汇编是一个坏主意......

关于c - 使用 cmpxchg8b 未获得 unsigned long 的预期输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37821072/

相关文章:

c - 测试和学习 C 中的图形时出现意外错误

gcc - 推送 ebp : operand type mismatch for `push'

assembly - 为什么我的代码显示垃圾?

C++11:atomic::compare_exchange_weak 是否支持非原始类型?

c - 在c中绘制二叉树到控制台

c - 在单调递增然后递减的序列cera中找到一个数字

assembly - 获取组装说明的大小

c++ - __transaction_atomic 未启用事务内存支持

c++ - 内存障碍,不确定我是否可以轻松使用?

c - 需要解释一下这段代码是如何工作的