Nice programing

x86 어셈블리에서 "잠금"명령은 무엇을 의미합니까?

nicepro 2020. 12. 29. 08:28
반응형

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. 인터넷 검색에서 lock명령으로 인해 CPU가 버스를 잠그는 것을 알았지 만 CPU가 언제 버스를 해제하는지 모르겠습니까?

  2. 위의 코드 전체에 대해이 코드가 Add?


  1. LOCK는 명령어 자체가 아닙니다. 다음 명령어에 적용되는 명령어 접두사입니다. 그 명령은 메모리 (의 읽기 - 수정 - 쓰기 않는 것이어야 INC, XCHG, CMPXCHG이 경우는입니다 --- 등)을 incl (%ecx)명령 increments l에서 개최 된 주소 옹 단어를 ecx등록합니다.

    LOCK접두사 보장하는 CPU는 작업 기간에 해당하는 캐시 라인의 배타적 인 소유권을 가지고 있으며, 특정 추가 주문 보증을 제공. 이는 버스 잠금을 지정하여 달성 할 수 있지만 CPU는 가능한 경우이를 방지합니다. 버스가 잠긴 경우 잠긴 명령이 지속되는 동안에 만 해당됩니다.

  2. 이 코드는 스택에서 증가 할 변수의 주소를 ecx레지스터 로 복사 한 다음 lock incl (%ecx)해당 변수를 원자 적으로 1 씩 증가시킵니다. 다음 두 명령어는 eax레지스터 (함수에서 반환 값을 보유 함)를 0으로 설정합니다. 변수의 새 값은 0이고 그렇지 않으면 1입니다. 작업은 추가 가 아니라 증분입니다 (따라서 이름).


이해하지 못할 수있는 것은 값을 증가시키는 데 필요한 마이크로 코드가 먼저 이전 값을 읽어야한다는 것입니다.

Lock 키워드는 실제로 발생하는 여러 마이크로 명령어를 원자 적으로 작동하는 것처럼 보이게합니다.

각각 동일한 변수를 증가시키려는 2 개의 스레드가 있고 둘 다 동시에 동일한 원래 값을 읽으면 둘 다 동일한 값으로 증가하고 둘 다 동일한 값을 씁니다.

변수를 두 번 증가시키는 대신 (일반적인 예상) 변수를 한 번 증가시킵니다.

lock 키워드는 이러한 일이 발생하지 않도록합니다.


Google에서 잠금 명령으로 인해 cpu가 버스를 잠그는 것을 알았지 만 cpu가 버스를 언제 해제할지 모르겠습니다.

LOCK는 명령어 접두사이므로 다음 명령어에만 적용되며 소스는 여기서 명확하게 설명하지 않지만 실제 명령어는 LOCK INC입니다. 따라서 버스는 증분 동안 잠긴 다음 잠금 해제됩니다.

위의 코드 전체에 대해이 코드가 어떻게 추가를 구현했는지 이해하지 못합니다.

그들은 Add를 구현하지 않고 이전 값이 0이면 반환 표시와 함께 증분을 구현합니다. 추가는 사용합니다 LOCK XADD(그러나 Windows InterlockedIncrement / Decrement도를 사용하여 구현됩니다 LOCK XADD).


최소 실행 가능한 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

From this we see that the LOCK prefix made the addition atomic: without it we have race conditions on many of the adds, and the total count at the end is less than the synchronized 20000.

See also: What does multicore assembly language look like?

Tested in Ubuntu 19.04 amd64.

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

반응형