2016-06-14 35 views
-1

我想写一个简单的比较和交换内联汇编代码。这里是我的代码没有得到预期的输出使用cmpxchg8b为无符号长

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

+1

是否http://stackoverflow.com/questions/6756985/correct-way-to-wrap-cmpxchg8b-in- gcc-inline-assembly-32位有帮助吗? – user200783

+1

您是否阅读过cmpxchg8b的文档:'将EDX:EAX与m64进行比较。如果相等,请设置ZF并将ECX:EBX加载到m64。否则,请清除ZF并将m64加载到EDX:EAX.'由于您没有将任何特定值加载到EDX(或EBX)中,因此我认为比较总是失败,这意味着asm不执行任何操作,并且'prev'(init为0在未优化的版本中)不变。此外,传递给cmpxchg8b的内存地址是'prev'(aka%0),而不是ptr,所以ptr从不使用。这可能是因为* ptr(vs ptr)可能不是有效的内存地址。 –

+0

另外,在你的平台(你说x86)上'unsigned long'多长时间?如果答案不是8字节,则需要重新考虑使用cmpxchg8b。哪里不对?我担心这几乎是一切。 –

回答

4

让我开始说“使用内联asm是一个坏主意。”让我重复一遍,“使用内联asm是一个坏主意。”你可以编写一个完整的wiki entry关于为什么使用内联asm是一个坏主意。请考虑使用内建函数(如gcc的__sync_bool_compare_and_swap)或者像<原子>这样的库。

如果您正在编写生产软件,使用内联asm的风险几乎肯定会大于任何收益。如果您正在为教育目的而写作,请继续阅读。 (为了进一步说明为什么你不应该使用内联asm,等待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] "[email protected]" (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并直接使用标志。有关详细信息,请参阅gcc docs

至于它是如何工作的:

当我们调用cmpxchg8b,我们通过它指向内存的指针。它将比较该存储单元中的(8字节)值与edx:eax中的8个字节。如果它们匹配,那么它会将ecx:ebx中的8个字节写入内存位置,并且将会设置zero标志。如果它们不匹配,那么当前值将以edx:eax返回并且zero标志将被清除。

所以,比较,与代码:

asm ("lock cmpxchg8b %[ptr]" 

在这里,我们传递的指针8个字节cmpxchg8b

 "setz %[result]" 

在这里,我们存储zero标志通过设定cmpxchg8b的内容到(结果)。

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

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

  "+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语句的工作原理。如果这些解释的部分内容仍不清楚,有些阅读可能会有所帮助。

BTW的情况下,我忘了提,写联汇编是一个糟糕的主意......

+0

哦来吧大卫链接到您的博客;-)大声笑 –

+1

呵呵,我想我会离开这一个单独;看起来像你有覆盖。尽管我的头像,我有时需要抑制自己试图纠正互联网上的所有错误。虽然,你确定你需要一个“内存”clobber吗?如果你对内存中可能被修改或不可修改的值使用“+ m”操作数,编译器将不得不重新加载它。可能使用'volatile moo *'是一个不错的选择。 –

+0

另外,你可以通过使用'“+ A”'约束来避免'union',这意味着'edx:eax'的组合作为64位值。但它更清晰,可移植到x86-64([其中64位值将进入rax或rdx](https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html))。 –

2

对不起,没有直接回答你的问题,但我的问题是:为什么不使用C11's <stdatomic.h> or C++11's <atomic>?与编写自己的函数相比,它更容易出错,并且具有不针对特定硬件体系结构或编译器的优点。

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

+0

你提到的这两个函数,它们返回布尔值。对于我的实现,我需要指向旧值* ptr。 – Ritesh

+0

这些功能也可用于此目的。第二个参数指向的变量将被覆盖以包含旧值。 –

+1

@Ritesh:请编辑你的问题,并添加为什么你不能使用该语言的标准功能,必须恢复为汇编代码。一般来说,编写自己的存根是个坏主意,因为它们可能会干扰编译器优化并导致代码更糟。更不用说它不便携。有应用笔记可以找到如何用stdatomics模拟CAS。但实际上,该标准提供了更多有用的功能,使得CAS仿真可能变得多余。 – Olaf