在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. 从谷歌搜索,我知道Add指令将导致CPU锁定总线,但是我不知道CPU何时释放总线?

  2. 关于上面的整个代码,我不明白该代码如何实现Add

gemfield asked 2019-11-08T00:41:17Z
4个解决方案
87 votes
  1. LOCK本身不是指令:它是指令前缀,适用于以下指令。 该指令必须是可以在内存上进行读-修改-写操作的东西(INCXCHGCMPXCHG等)-在这种情况下,是ecx指令,该指令lock incl (%ecx)会对eax602中保存在2604427652039705705602寄存器中的单词进行修改。

    ecx前缀可确保CPU在操作期间对相应的高速缓存行具有排他性所有权,并提供某些其他排序保证。 这可以通过声明总线锁定来实现,但是CPU会尽可能避免这种情况。 如果总线被锁定,则仅在锁定指令期间。

  2. 此代码将要从堆栈中递增的变量的地址复制到ecx寄存器中,然后执行lock incl (%ecx)以原子方式将该变量递增1。接下来的两条指令将eax寄存器(保存了函数的返回值)设置为 如果变量的新值为0,则为0,否则为1。 该操作是增量,而不是加法(因此为名称)。

Anthony Williams answered 2019-11-08T00:41:50Z
12 votes

您可能无法理解的是,递增值所需的微码要求我们首先读取旧值。

Lock关键字强制实际发生的多个微指令似乎是原子操作的。

如果您有2个线程分别尝试递增相同的变量,并且它们都同时读取相同的原始值,那么它们都将递增为相同的值,并且都写出相同的值。

您不必将变量递增两次(这是通常的期望),而是最终将变量递增一次。

lock关键字可以防止这种情况的发生。

Dan answered 2019-11-08T00:42:42Z
10 votes

从谷歌,我知道锁定指令将导致CPU锁定总线,但我   不知道cpu什么时候有空?

LOCK XADD是指令前缀,因此仅适用于以下指令,此处的源代码并不清楚,但实际指令为LOCK XADD。因此,总线被锁定以递增,然后解锁

关于上面的整个代码,我不明白这些代码如何   实施了添加?

他们没有实现加号,而是实现了增量,以及如果旧值是0,则实现了返回指示。添加将使用LOCK XADD(但是,窗口InterlockedIncrement / Decrement也将通过LOCK XADD实现)。

Necrolis answered 2019-11-08T00:43:28Z
1 votes

最少的可运行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中测试。

translate from https://stackoverflow.com:/questions/8891067/what-does-the-lock-instruction-mean-in-x86-assembly