2016-06-14 7 views
-1

간단한 비교 및 ​​스왑 인라인 어셈블리 코드를 작성하려고합니다. 다음은 내 코드입니다부호없는 long에 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-bits는 도움이됩니까? – user200783

+1

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)이 유효한 메모리 주소가 아닐 수도 있기 때문일 것입니다. –

+0

또한 플랫폼에서'unsigned long '이 얼마나 걸릴 수 있습니까 (x86이라고 말합니까?). 대답이 8 바이트가 아니면 cmpxchg8b를 사용하여 다시 고려해야합니다. 뭐가 잘못 되었 니? 거의 모든 것이 두려워요. –

답변

4

"인라인 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]" 

우리 (결과)에 의해 설정된 cmpxchg8bzero 플래그의 내용을 저장한다.

 : [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 경우에 나는

+0

오, 당신의 블로그에 데이빗 링크가 있습니다 .-)Lol –

+1

hehe, 나는이 하나를 내버려 둘 것입니다; 당신이 덮은 것처럼 보입니다. 내 아바타에도 불구하고 때로는 인터넷에서 잘못된 * 모든 것을 바로 잡으려고 스스로를 억제해야합니다. 그래도, 당신은''기억 장치''clobber가 필요 하겠는가? 수정 될 수도 있고 수정되지 않을 수도있는 메모리 값에 대해 + ""피연산자를 사용한다면, 컴파일러는 그것을 다시로드해야 할 것이다. 아마'volatile moo * '를 사용하는 것이 좋은 선택입니다. –

+0

또한''+ A "'제약 조건을 사용하여'union '을 피할 수 있습니다. 즉,'edx : eax'를 64 비트 값으로 사용하는 것입니다. 하지만 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 : 질문을 편집하고 언어의 표준 기능을 사용할 수없는 이유를 추가하고 어셈블러 코드로 되돌려 야합니다. 일반적으로 컴파일러 최적화를 방해하고 코드가 악화 될 수 있으므로 자신의 스텁을 작성하는 것은 좋지 않습니다. 말할 것도없이 휴대용이 아닙니다. CAS를 stdatomics와 에뮬레이트하는 방법에 대한 응용 노트가 있습니다. 그러나 실제로이 표준은 CAS 에뮬레이션을 불필요하게 만들 수있는보다 유용한 기능을 제공합니다. – Olaf