未使用 cmpxchg8b 获得无符号长型的预期输出
Not getting expected output using cmpxchg8b for unsigned long
我正在尝试编写一个简单的比较和交换内联汇编代码。这是我的代码
#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。我的代码有什么问题?请帮忙。
很抱歉没有直接回答你的问题,但我的问题是:为什么不使用 C11's <stdatomic.h>
or C++11's <atomic>
?与编写您自己的函数相比,它更不容易出错,并且具有您不针对特定硬件架构或编译器的优势。
在您的情况下,您应该使用 atomic_compare_exchange_weak()
或 atomic_compare_exchange_strong()
。
让我先说 "Using inline asm is a bad idea." 让我重复一遍 "Using inline asm is a bad idea." 你可以写一个完整的 wiki entry about why using inline asm is a bad idea. Please consider using builtins (like gcc's __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 失败(因为值不匹配),函数的 return 值为 0,您 需要 使用的值为return编辑于 oldval。这使得再次尝试变得简单。请注意,如果您是 运行 多线程(您必须是,否则您不会使用
lock cmpxchg8b
),第二次尝试也可能会失败,因为 'other' 线程可能又打败你了。
__GCC_ASM_FLAG_OUTPUTS__
定义可用于较新版本的 gcc (6.x+)。它允许您跳过执行 setz
并直接使用标志。有关详细信息,请参阅 gcc docs。
至于它是如何工作的:
当我们调用cmpxchg8b
时,我们将一个指向内存的指针传递给它。它将将该内存位置中的(8 字节)值与 edx:eax 中的 8 字节值进行比较。如果它们匹配,则它将 ecx:ebx 中的 8 个字节写入内存位置,并设置 zero
标志。如果它们不匹配,则当前值将被 return 编辑到 edx:eax 并且 zero
标志将被清除。
因此,将其与代码进行比较:
asm ("lock cmpxchg8b %[ptr]"
这里我们将指向 8 个字节的指针传递给 cmpxchg8b
。
"setz %[result]"
这里我们将 cmpxchg8b
设置的 zero
标志的内容存储到 (result) 中。
: [result] "=q" (result), [ptr] "+m" (*ptr),
指定(result)是一个输出(=),并且它必须是一个字节寄存器(q)。此外,内存指针是一个输入+输出(+),因为我们将同时读取和写入它。
"+d" (oldval->upper), "+a"(oldval->lower)
+ 号再次表示这些值是 in+out。这是必要的,因为如果比较失败,edx:eax 将被 ptr 的当前值覆盖。
: "c" (newval->upper), "b"(newval->lower)
这些值仅供输入。 cmpxchg8b
不会更改它们的值,因此我们将它们放在第二个冒号之后。
: "cc", "memory");
由于我们正在更改标志,因此我们需要通过 "cc" 通知编译器。 "memory" 约束可能不是必需的,具体取决于 cas 的用途。线程 1 可能会通知线程 2 某些内容已准备好进行处理。在这种情况下,您要绝对确保 gcc 在寄存器中没有任何它计划稍后写入内存的值。在执行cmpxchg8b
.
之前,它绝对必须将它们全部刷新到内存
gcc docs 详细描述了扩展 asm 语句的工作原理。如果此解释的某些部分仍然不清楚,阅读一些内容可能会有所帮助。
顺便说一句,以防我忘记提及,编写内联 asm 是个坏主意...
我正在尝试编写一个简单的比较和交换内联汇编代码。这是我的代码
#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。我的代码有什么问题?请帮忙。
很抱歉没有直接回答你的问题,但我的问题是:为什么不使用 C11's <stdatomic.h>
or C++11's <atomic>
?与编写您自己的函数相比,它更不容易出错,并且具有您不针对特定硬件架构或编译器的优势。
在您的情况下,您应该使用 atomic_compare_exchange_weak()
或 atomic_compare_exchange_strong()
。
让我先说 "Using inline asm is a bad idea." 让我重复一遍 "Using inline asm is a bad idea." 你可以写一个完整的 wiki entry about why using inline asm is a bad idea. Please consider using builtins (like gcc's __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 失败(因为值不匹配),函数的 return 值为 0,您 需要 使用的值为return编辑于 oldval。这使得再次尝试变得简单。请注意,如果您是 运行 多线程(您必须是,否则您不会使用
lock cmpxchg8b
),第二次尝试也可能会失败,因为 'other' 线程可能又打败你了。 __GCC_ASM_FLAG_OUTPUTS__
定义可用于较新版本的 gcc (6.x+)。它允许您跳过执行setz
并直接使用标志。有关详细信息,请参阅 gcc docs。
至于它是如何工作的:
当我们调用cmpxchg8b
时,我们将一个指向内存的指针传递给它。它将将该内存位置中的(8 字节)值与 edx:eax 中的 8 字节值进行比较。如果它们匹配,则它将 ecx:ebx 中的 8 个字节写入内存位置,并设置 zero
标志。如果它们不匹配,则当前值将被 return 编辑到 edx:eax 并且 zero
标志将被清除。
因此,将其与代码进行比较:
asm ("lock cmpxchg8b %[ptr]"
这里我们将指向 8 个字节的指针传递给 cmpxchg8b
。
"setz %[result]"
这里我们将 cmpxchg8b
设置的 zero
标志的内容存储到 (result) 中。
: [result] "=q" (result), [ptr] "+m" (*ptr),
指定(result)是一个输出(=),并且它必须是一个字节寄存器(q)。此外,内存指针是一个输入+输出(+),因为我们将同时读取和写入它。
"+d" (oldval->upper), "+a"(oldval->lower)
+ 号再次表示这些值是 in+out。这是必要的,因为如果比较失败,edx:eax 将被 ptr 的当前值覆盖。
: "c" (newval->upper), "b"(newval->lower)
这些值仅供输入。 cmpxchg8b
不会更改它们的值,因此我们将它们放在第二个冒号之后。
: "cc", "memory");
由于我们正在更改标志,因此我们需要通过 "cc" 通知编译器。 "memory" 约束可能不是必需的,具体取决于 cas 的用途。线程 1 可能会通知线程 2 某些内容已准备好进行处理。在这种情况下,您要绝对确保 gcc 在寄存器中没有任何它计划稍后写入内存的值。在执行cmpxchg8b
.
gcc docs 详细描述了扩展 asm 语句的工作原理。如果此解释的某些部分仍然不清楚,阅读一些内容可能会有所帮助。
顺便说一句,以防我忘记提及,编写内联 asm 是个坏主意...