2016-06-14 37 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