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
인터넷 검색에서
lock
명령으로 인해 CPU가 버스를 잠그는 것을 알았지 만 CPU가 언제 버스를 해제하는지 모르겠습니까?위의 코드 전체에 대해이 코드가
Add
?
LOCK
는 명령어 자체가 아닙니다. 다음 명령어에 적용되는 명령어 접두사입니다. 그 명령은 메모리 (의 읽기 - 수정 - 쓰기 않는 것이어야INC
,XCHG
,CMPXCHG
이 경우는입니다 --- 등)을incl (%ecx)
명령inc
rementsl
에서 개최 된 주소 옹 단어를ecx
등록합니다.LOCK
접두사 보장하는 CPU는 작업 기간에 해당하는 캐시 라인의 배타적 인 소유권을 가지고 있으며, 특정 추가 주문 보증을 제공. 이는 버스 잠금을 지정하여 달성 할 수 있지만 CPU는 가능한 경우이를 방지합니다. 버스가 잠긴 경우 잠긴 명령이 지속되는 동안에 만 해당됩니다.이 코드는 스택에서 증가 할 변수의 주소를
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;
}
컴파일 및 실행 :
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
'Nice programing' 카테고리의 다른 글
교리 2에서 WHERE .. IN 하위 쿼리 수행 (0) | 2020.12.29 |
---|---|
단일 POM에서 두 개의 다른 maven exec-maven-plugin을 실행할 수 있습니까? (0) | 2020.12.29 |
Flask의 우수한 디버그 로그 메시지를 프로덕션 파일에 어떻게 작성합니까? (0) | 2020.12.29 |
카르타고 카트 파일을 올바르게 만드는 방법은 무엇입니까? (0) | 2020.12.29 |
구성 요소에서 사용되는 지시문에 대한 참조 가져 오기 (0) | 2020.12.29 |