如何在c中使用'cmpxchg8b'汇编指令实现原子比较和交换?

时间:2016-06-14 04:35:29

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

我正在尝试实现无锁链接列表。为此,我需要在C中使用内联汇编来实现原子比较和交换指令。我知道我需要对cmpxchg8b使用x86指令,但是我无法做到。

我的节点结构如下:

typedef struct node
{
    int data;
    struct node * next;
    struct node * backlink;
}node_lf;

struct node * next指针还包含最后2位(标记和标志位)的附加信息

我需要实现的比较和交换是这样的:

node_lf *cs (cs_arg * address, cs_arg *old_val, cs_arg *new_val )
{
    node_lf *value = (address->node)->next;
    if ((address->node->next) == old_val->node)
    {
        (address->node)->next = new_val->node;
    }
    return value;
}

cs_arg结构如下:

typedef struct csArg
{
    node_lf * node;
}cs_arg;

我的实施:

static inline node_lf* cs(cs_arg * address, cs_arg *old_val, cs_arg *new_val)
{
    node_lf * value;
    __asm__ __volatile__("lock cmpxchg8b %0; "
                :"=q"(value)
                :"a"(address->node->next), "b"(old_val->node), "c"(new_val->node)
                :"memory");
    return value;
}

这会显示错误消息: ../list.c:86: Error: operand type mismatch for 'cmpxchg8b' make: *** [list.o] Error 1

请帮我解决这个问题。

1 个答案:

答案 0 :(得分:2)

让我们一步一步走。首先,让我们尝试在一个简单的例子中使用__sync_val_compare_and_swap:

#include <stdio.h>

typedef struct node
 {
    int data;
    struct node * next;
    struct node * backlink;
 }node_lf;

int cs1(node_lf *address, int old_val, int new_val)
{
    int *p2 = &address->data;
    int p3 = __sync_val_compare_and_swap(p2, old_val, new_val);  

    return p3;
}

int main()
{
   node_lf n;

   n.data = 17;

   int res = cs1(&n, 1, 2);
   printf("Old Value: %d  Cur Value: %d\n", res, n.data);

   res = cs1(&n, 17, 2);
   printf("Old Value: %d  Cur Value: %d\n", res, n.data);
}

所以,这里的代码非常简单。我们正在操纵node.data。我们首先将其初始化为17.然后我们尝试执行cas将其更改为2.但是,在第一次调用中,我们为oldval提供了不正确的值(1 vs 17)。因此,__sync_val_compare_and_swap(正确!)不执行交换,但会返回当前值。第二个调用确实提供了正确的旧值,它确实执行交换并返回旧值。

所以,当我们跑步时,我们得到:

Old Value: 17  Cur Value: 17 <- Didn't change the value
Old Value: 17  Cur Value: 2  <- Did change the value

很简单,但不是你想要的。让我们更接近一点:

#include <stdio.h>

typedef struct node
 {
    int data;
    struct node * next;
    struct node * backlink;
 }node_lf;

typedef struct csArg
{
    node_lf * node;
}cs_arg;

node_lf *cs2(node_lf *address, const cs_arg *old_val, const cs_arg *new_val)
{
    unsigned long long *p2 = (unsigned long long *)&address->next;
    unsigned long long p3 = __sync_val_compare_and_swap (p2, (unsigned long long)old_val->node, (unsigned long long)new_val->node);  

    return (node *)p3;
}

int main()
{
   node_lf n;
   cs_arg oldval, newval;

   n.next = (node *)18;
   oldval.node = (node *)1;
   newval.node = (node *)2;

   node *res2 = cs2(&n, &oldval, &newval);
   printf("Old Value: %p  Cur Value: %p\n", res2, n.next);

   oldval.node = (node *)18;
   res2 = cs2(&n, &oldval, &newval);
   printf("Old Value: %p  Cur Value: %p\n", res2, n.next);
}

在这种情况下,我们尝试使用2 cs_args中的节点更新node.next。这里要注意的主要事情是,由于__sync_val_compare_and_swap适用于整数类型,我们需要将指针强制转换为适当大小的int。这里的假设是我们使用64位编译器,因此指针是8个字节(与unsigned long long相同)。

这里没有必要使用cs_arg结构。使用node *应该可以正常工作。但你使用它,所以可能在这里有一些长期计划。

运行这个我们看到:

Old Value: 0000000000000012  Cur Value: 0000000000000012  <- Didn't change the value
Old Value: 0000000000000012  Cur Value: 0000000000000002  <- Did change the value

请注意,如果您运行的是x64,则最多可以将其缓冲到16个字节(使用__int128和cmpxchg16b)。但是,有一些技巧需要注意。例如,数据必须是16byte对齐的(哪个node.next是NOT)。

现在,完成所有这些后,您“需要将旧版的address-&gt; node-&gt; next作为返回值”的要求似乎是可疑的。看看上面的代码,你如何判断cas是否失败?如果它工作,它返回18,如果它失败,它返回18.因为那就是你所说的你想要的,这就是我试图提供的。

但我认为你确实想知道它是否有效(可能是为了再试一次)。在这种情况下,也许更像是这样:

#include <stdio.h>

typedef struct node
 {
    int data;
    struct node * next;
    struct node * backlink;
 }node_lf;

bool cs2(node_lf *address, const node *old_val, const node *new_val)
{
    unsigned long long *p2 = (unsigned long long *)&address->next;
    return __sync_bool_compare_and_swap (p2, (unsigned long long)old_val, (unsigned long long)new_val);
}

int main()
{
   node_lf n;
   node *oldval, *newval;

   n.next = (node *)18;
   oldval = (node *)1;
   newval = (node *)2;

   bool res2;

   do {

      printf("Trying to change %p from %p to %p: ", n.next, oldval, newval);
      res2 = cs2(&n, oldval, newval);
      printf("Worked: %d  Cur Value: %p\n", res2, n.next);
      if (res2)
         break;

      oldval = n.next;
   } while (1);
}

退出循环时,oldval将是之前的(必须或cas会失败),newval将是实际写入的内容。请注意,如果这真的是多线程的,则无法保证newval与n.next相同,因为另一个线程可能已经出现并再次更改它。

虽然使用汇编程序可能能够为您节省一两条指令,但在可读性,可维护性,可移植性等方面的胜利几乎肯定是值得的。

除非这是一个教师需要内联asm的课程项目。在这种情况下,请查看https://stackoverflow.com/a/37825052/2189500