web-dev-qa-db-ja.com

x86アセンブリで「ロック」命令は何を意味しますか?

Qtのソースにx86アセンブリがいくつかありました。

q_atomic_increment:
    movl 4(%esp), %ecx
    lock 
    incl (%ecx)
    mov $0,%eax
    setne %al
    ret

    .align 4,0x90
    .type q_atomic_increment,@function
    .size   q_atomic_increment,.-q_atomic_increment
  1. Googlingから、lock命令によりCPUがバスをロックすることはわかっていましたが、CPUがいつバスを解放するかわかりませんか?

  2. 上記のコード全体について、このコードがAddを実装する方法がわかりません。

53
gemfield
  1. LOCKは命令そのものではありません。これは、次の命令に適用される命令プレフィックスです。その命令は、メモリ上で読み取り-変更-書き込みを行うものでなければなりません(INCXCHGCMPXCHGなど)---この場合はincl (%ecx)命令。

    LOCKプレフィックスは、CPUが操作中に適切なキャッシュラインの排他的所有権を持つことを保証し、特定の追加の順序保証を提供します。これはバスロックをアサートすることで実現できますが、CPUはこれを可能な限り回避します。バスがロックされている場合、ロックされた命令の期間のみです。

  2. このコードは、スタックからインクリメントされる変数のアドレスをecxレジスタにコピーし、lock incl (%ecx)を実行してその変数をアトミックに1インクリメントします。次の2つの命令はeaxレジスタ(関数からの戻り値を保持する)は、変数の新しい値が0の場合は0に、それ以外の場合は1になります。操作はincrementであり、追加ではありません(名前の由来です)。

81

理解できない可能性があるのは、値をインクリメントするために必要なマイクロコードでは、最初に古い値を読み込む必要があるということです。

Lockキーワードは、実際に発生している複数のマイクロ命令をアトミックに動作するように強制します。

それぞれが同じ変数をインクリメントしようとする2つのスレッドがあり、両方が同じ元の値を同時に読み取った場合、両方が同じ値にインクリメントし、両方が同じ値を書き出します。

通常の予想である変数を2回インクリメントする代わりに、変数を1回インクリメントします。

Lockキーワードは、これが起こらないようにします。

12
Dan

Googleから、私はロック命令がCPUをバスにロックさせることを知っていましたが、CPUがバスをいつ解放するかわかりませんか?

LOCKは命令プレフィックスであるため、次の命令にのみ適用されます。ソースはここでは明確にしませんが、実際の命令は_LOCK INC_です。したがって、バスは増分のためにロックされ、その後ロック解除されます

上記のコード全体について、これらのコードがどのように追加を実装したのか理解できませんか?

それらは、Addを実装せず、増分を実装し、古い値が0の場合は戻り値を表示します。加算は_LOCK XADD_を使用します(ただし、Windows InterlockedIncrement/Decrementも_LOCK XADD_で実装されます。 )。

10
Necrolis

最小限の実行可能なC++スレッド+ LOCKインラインアセンブリの例

main.cpp

#include <atomic>
#include <cassert>
#include <iostream>
#include <thread>
#include <vector>

std::atomic_ulong my_atomic_ulong(0);
unsigned long my_non_atomic_ulong = 0;
unsigned long my_Arch_atomic_ulong = 0;
unsigned long my_Arch_non_atomic_ulong = 0;
size_t niters;

void threadMain() {
    for (size_t i = 0; i < niters; ++i) {
        my_atomic_ulong++;
        my_non_atomic_ulong++;
        __asm__ __volatile__ (
            "incq %0;"
            : "+m" (my_Arch_non_atomic_ulong)
            :
            :
        );
        __asm__ __volatile__ (
            "lock;"
            "incq %0;"
            : "+m" (my_Arch_atomic_ulong)
            :
            :
        );
    }
}

int main(int argc, char **argv) {
    size_t nthreads;
    if (argc > 1) {
        nthreads = std::stoull(argv[1], NULL, 0);
    } else {
        nthreads = 2;
    }
    if (argc > 2) {
        niters = std::stoull(argv[2], NULL, 0);
    } else {
        niters = 10000;
    }
    std::vector<std::thread> threads(nthreads);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i] = std::thread(threadMain);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i].join();
    assert(my_atomic_ulong.load() == nthreads * niters);
    assert(my_atomic_ulong == my_atomic_ulong.load());
    std::cout << "my_non_atomic_ulong " << my_non_atomic_ulong << std::endl;
    assert(my_Arch_atomic_ulong == nthreads * niters);
    std::cout << "my_Arch_non_atomic_ulong " << my_Arch_non_atomic_ulong << std::endl;
}

GitHubアップストリーム

コンパイルして実行します:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp -pthread
./main.out 2 10000

可能な出力:

my_non_atomic_ulong 15264
my_Arch_non_atomic_ulong 15267

これから、LOCKプレフィックスが加算をアトミックにしたことがわかります。これがないと、多くの加算で競合状態が発生し、最後の合計カウントは同期20000未満になります。

参照: マルチコアアセンブリ言語はどのように見えますか?

Ubuntu 19.04 AMD64でテスト済み。