"인라인 asm을 사용하는 것은 나쁜 생각입니다."라는 말로 시작하겠습니다. "인라인 asm 사용은 나쁜 생각입니다." asm을 사용하는 것이 나쁜 생각 인 이유에 대해 전체 wiki entry을 작성할 수 있습니다. 내장형 (gcc의 __sync_bool_compare_and_swap과 같은) 또는 < 원자형 >과 같은 라이브러리를 대신 사용해보십시오.
프로덕션 소프트웨어를 작성하는 경우 인라인 asm 사용의 위험이 모든 이점보다 훨씬 큽니다. 교육 목적으로 글을 쓰고 있다면 계속 읽어보십시오.
(인라인 asm을 사용하지 않아야하는 이유에 대한 자세한 설명은 마이클 또는 피터가 나타나서이 코드의 모든 점을 잘못 표시 할 때까지 기다려주십시오. 물건을 얻으려면 오른쪽으로 치십시오.)
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]"
우리가 cmpxchg8b
에 8 바이트 포인터를 전달하는 :
그래서, 코드와 것을 비교한다. 여기
"setz %[result]"
우리 (결과)에 의해 설정된 cmpxchg8b
zero
플래그의 내용을 저장한다.
: [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"가 사용되는 지에 따라 "memory"제약 조건이 필요하지 않을 수도 있습니다. 스레드 1이 처리 할 준비가되었음을 스레드 2에 알리는 것이 가능할 수 있습니다. 이 경우 나중에 gcc가 메모리에 쓸 계획 인 레지스터에 gcc에 값이 없는지 확실히하고 싶습니다. cmpxchg8b
을 실행하기 전에 반드시 을 모두 메모리로 플러시해야합니다.
gcc docs은 확장 된 asm 문의 작동 방식을 자세히 설명합니다. 이 설명의 일부분이 여전히 명확하지 않은 경우, 약간의 독서가 도움이 될 수 있습니다.
이 BTW 경우에 나는
합니까 ... 나쁜 생각은 인라인 어셈블리를 작성, 얘기를 깜빡 했네요 http://stackoverflow.com/questions/6756985/correct-way-to-wrap-cmpxchg8b-in- gcc-inline-assembly-32-bits는 도움이됩니까? – user200783
cmpxchg8b :'EDX : EAX와 m64 비교 '에 대한 문서를 읽어 보셨습니까? 동일하면 ZF를 설정하고 ECX : EBX를 m64에로드하십시오. 그렇지 않으면 ZF를 지우고 m64를 EDX : EAX에로드하십시오. EDX (또는 EBX)에 특정 값을로드하지 않으므로 asm이 아무것도하지 않는다는 것을 의미하는 비교가 항상 실패한 것으로 가정하고 'prev' (init 최적화되지 않은 빌드에서)는 변경되지 않고 반환됩니다. 또한 cmpxchg8b로 전달할 메모리 주소는 ptr이 아니라'prev' (일명 % 0)이므로 ptr은 사용되지 않습니다. 아마도 * ptr (vs ptr)이 유효한 메모리 주소가 아닐 수도 있기 때문일 것입니다. –
또한 플랫폼에서'unsigned long '이 얼마나 걸릴 수 있습니까 (x86이라고 말합니까?). 대답이 8 바이트가 아니면 cmpxchg8b를 사용하여 다시 고려해야합니다. 뭐가 잘못 되었 니? 거의 모든 것이 두려워요. –