使用cmpxchg8b获得无符号长度的预期输出

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

#include  #include  #include  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.我的代码有什么问题?请帮助。

首先让我说“使用内联asm是一个坏主意。” 让我再说一遍“使用内联asm是一个坏主意。” 你可以编写一个完整的wiki条目,了解为什么使用inline asm是一个坏主意。 请考虑使用builtins (如gcc的__sync_bool_compare_and_swap )或像这样的库。

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

(为了进一步说明为什么你不应该使用内联asm,等待迈克尔或彼得出现并指出这些代码的所有错误。即使对于那些知道这些东西的人来说,也很难做到正确。)

以下是一些显示如何使用cmpxchg8b代码。 这很简单,但应该足以给出一个大致的想法。

 #include  // 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中返回。 这使得再次尝试变得简单。 请注意,如果您正在运行multithreading(您必须使用或者您不会使用lock cmpxchg8b ),则第二次尝试也可能会失败,因为“其他”线程可能会再次击败您。
  • __GCC_ASM_FLAG_OUTPUTS__定义适用于较新版本的gcc(6.x +)。 它允许您跳过执行setz并直接使用标志。 有关详细信息,请参阅gcc 文档 。

至于它是如何工作的:

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

所以,将它与代码进行比较:

  asm ("lock cmpxchg8b %[ptr]" 

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

  "setz %[result]" 

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

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

指定(result)是输出(=),并且它必须是字节寄存器(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文档详细描述了扩展的asm语句的工作方式。 如果部分解释仍不清楚,一些阅读可能会有所帮助。

如果我忘了提及BTW,写内联asm是个坏主意……

很抱歉没有直接回答你的问题,但我的问题是:为什么不使用C11的或C ++ 11的 ? 它比编写自己的函数更不容易出错,并且具有不针对特定硬件架构或编译器的优势。

在您的情况下,您应该使用atomic_compare_exchange_weak()atomic_compare_exchange_strong()