簡體   English   中英

x86 匯編中的“lock”指令是什么意思?

[英]What does the “lock” instruction mean in x86 assembly?

我在 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. 從谷歌搜索,我知道lock指令會導致 CPU 鎖定總線,但我不知道 CPU 何時釋放總線?

  2. 關於上面的整個代碼,我不明白這段代碼是如何實現Add

  1. LOCK本身不是一條指令:它是一個指令前綴,適用於后面的指令。 該指令必須的東西做一個讀-修改-寫內存( INCXCHGCMPXCHG等)---在這種情況下,它是incl (%ecx)其中指令inc rements的l舉行的地址翁詞在ecx寄存器中。

    LOCK前綴確保 CPU 在操作期間對適當的緩存行擁有獨占所有權,並提供某些額外的排序保證。 這可以通過斷言總線鎖定來實現,但 CPU 將在可能的情況下避免這種情況。 如果總線被鎖定,那么它只是在鎖定指令的持續時間內。

  2. 此代碼將ecx堆棧遞增的變量的地址復制到ecx寄存器中,然后lock incl (%ecx)以將該變量自動遞增 1。接下來的兩條指令設置eax寄存器(保存返回值)如果變量的新值為 0,則從函數)到 0,否則為 1。 該操作是increment ,而不是 add (因此得名)。

您可能無法理解的是,增加值所需的微代碼要求我們首先讀取舊值。

Lock 關鍵字強制實際發生的多條微指令以原子方式操作。

如果您有 2 個線程,每個線程都試圖增加相同的變量,並且它們同時讀取相同的原始值,那么它們都增加到相同的值,並且它們都寫出相同的值。

不是讓變量增加兩次,這是典型的期望,你最終增加了一次變量。

lock 關鍵字可防止這種情況發生。

從谷歌,我知道鎖定指令會導致 cpu 鎖定總線,但我不知道 cpu 何時釋放總線?

LOCK是一個指令前綴,因此它只適用於下面的指令,來源在這里沒有說得很清楚,但真正的指令是LOCK INC 所以總線被鎖定為增量,然后解鎖

關於上面的整個代碼,我不明白這些代碼是如何實現Add的?

它們不實現 Add ,它們實現了一個增量,如果舊值為 0 則返回指示。添加將使用LOCK XADD (但是,windows InterlockedIncrement/Decrement 也使用LOCK XADD實現)。

最小可運行 C++ 線程 + LOCK 內聯匯編示例

主程序

#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。

LOCK 前綴用於實現:

另請參閱: 多核匯編語言是什么樣的?

在 Ubuntu 19.04 amd64 中測試。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM