2016-06-14 1 views
-1

単純な比較とスワップインラインアセンブリコードを作成しようとしています。ここに私のコードは、このコードは、理想的に5を印刷する必要があり符号なしロングに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; 
} 

ですが、私のコードで何が間違っている0を印刷している?助けてください。

+1

はhttp://stackoverflow.com/questions/6756985/correct-way-to-wrap-cmpxchg8b-in-をいgcc-inline-assembly-32-bitは役に立ちますか? – user200783

+1

cmpxchg8bのドキュメントを読んだことがありますか?「EDX:EAXとm64を比較してください。等しい場合は、ZFを設定し、ECX:EBXをm64にロードします。それ以外の場合は、ZFをクリアし、m64をEDX:EAXにロードします。EDX(またはEBX)に特定の値をロードしていないので、asmが何もしないことを意味するcompareが常に失敗し、 'prev'最適化されていないビルドでは)変更されません。さらに、cmpxchg8bに渡すメモリアドレスはptrではなく 'prev'(別名%0)なので、ptrは決して使用されません。 * ptr(vs ptr)はおそらく有効なメモリアドレスではないため、おそらく同様です。 –

+0

また、あなたのプラットフォーム(x86と言っています)の 'unsigned long'はどれくらいですか?答えが8バイトでない場合は、cmpxchg8bを使用して再考する必要があります。なにが問題ですか?私はそれがほとんどすべてであることが怖いです。 –

答えて

4

「インラインアスミックを使用するのは悪い考えです」と言って始めましょう。また、「インライン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を使用していない場合)、もう1つの試みが失敗することも考えられます。なぜなら、「他の」スレッドはあなたをもう一度書き込むことができたからです。
  • __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(+)になっています。読み書きと書き込みの両方になるからです。

+記号は、これらの値が再び+ outにあることを示します。比較が失敗した場合、edx:eaxはptrの現在の値で上書きされるため、これは必要です。

 : "c" (newval->upper), "b"(newval->lower) 

これらの値は入力専用です。 cmpxchg8bは値を変更しないので、2番目のコロンの後に入れます。

 : "cc", "memory"); 

フラグを変更しているので、コンパイラに「cc」で通知する必要があります。正確にどのcasが使用されているかに応じて、 "memory"制約は必要ないかもしれません。スレッド1が何らかの処理の準備ができていることをスレッド2に通知している可能性があります。その場合、後でメモリに書き込む予定のレジスタにgccに値がないことを絶対に確認したいとします。 cmpxchg8bを実行する前に、すべてをメモリにフラッシュする必要があります。

gcc docsは、拡張asmステートメントの動作を詳しく説明しています。この説明の一部がまだ不明な部分がある場合は、いくつかの読者が役に立つかもしれません。私は言及を忘れてしまった場合、ところで

、インラインアセンブラを書くことは悪い考えです...

+0

ああ、あなたのブログへのDavidのリンクに来る;-)笑 –

+1

hehe、私はこの1つだけを残すと思います。あなたがそれをカバーしているように見えます。私のアバターにもかかわらず、時々、インターネット上で間違っているものすべてを修正しようとすることを控える必要があります。しかし、あなたは本当に ''メモリ '' clobberが必要ですか?変更してもしなくてもよいメモリ内の値に対して+ ""オペランドを使うと、コンパイラはそれをリロードする必要があります。おそらく 'volatile moo * 'を使うのは良い選択です。 –

+0

また、 '' + ''制約を使うことで '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

上記の2つの関数はブール値を返します。私の実装のために、私は古い値の* ptrが必要です。 – Ritesh

+0

これらの機能は、その目的にも使用できます。 2番目の引数が指す変数は、古い値を含むように上書きされます。 –

+1

@Ritesh:質問を編集して、言語の標準機能を使用できない理由と、アセンブラコードに戻す必要がある理由を追加してください。一般に、コンパイラの最適化を妨げたり、コードが悪化したりする可能性があるため、独自のスタブを書くのは悪い考えです。ポータブルではありません。 stdatomicsを使用してCASをエミュレートする方法については、アプリケーションノートがあります。しかし、実際には、CASエミュレーションが余分になる可能性があるように、より標準的な機能が提供されています。 – Olaf

関連する問題