01 · QUESTION
무엇을 확인할 것인가
같은 atomic_add_return()이 값의 원자성뿐 아니라 어느 memory ordering까지 제공하는가?
원자적 read-modify-write와 memory order는 별개 축이다. relaxed는 tear/lost-update를 막지만 주변 일반 load/store의 order를 전부 보장하지 않는다. lock slow path는 contention queue와 fairness를 추가한다.
atomic variable의 cache line, retry/queue state와 critical section data를 구분한다. lock acquire 이전과 release 이후 access가 compiler와 CPU 양쪽에서 이동하지 않게 해야 한다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | LSE atomic 또는 LL/SC alternative | exclusive reservation이 잦게 깨지거나 잘못된 relaxed variant를 사용해 protected data가 늦게 보인다. | LSE capability, selected alternative, retry count, cache line owner와 acquire/release suffix를 본다. |
| x86-64 | LOCK prefix RMW와 CMPXCHG/XCHG | false sharing으로 unrelated atomic이 같은 line을 왕복하거나 PV hook 불일치로 lockup이 난다. | LOCK instruction, cache line, qspinlock val, pending/tail, holder CPU와 PV mode를 확인한다. |
| RISC-V | AMO aq/rl 또는 LR/SC와 Zacas capability | reservation granule contention이나 misaligned atomic이 progress/fault 문제를 만든다. | AMO width, aq/rl, LR/SC retry, reservation granule와 ISA extension을 확인한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- LSE atomic 또는 LL/SC alternative
- state
- LSE CPU는 LDADD/CAS 계열의 acquire/release suffix를 사용하고, 미지원 CPU는 load-exclusive/store-exclusive retry loop를 alternatives로 선택한다. qspinlock은 공통 algorithm 위에 이 primitive를 사용한다.
- checkpoint
- LSE capability, selected alternative, retry count, cache line owner와 acquire/release suffix를 본다.
x86-64
- mechanism
- LOCK prefix RMW와 CMPXCHG/XCHG
- state
- cache-coherent locked operation이 원자성과 강한 ordering을 제공한다. qspinlock은 fast cmpxchg 뒤 pending/tail queue를 사용하며 paravirt slow path가 대체될 수 있다.
- checkpoint
- LOCK instruction, cache line, qspinlock val, pending/tail, holder CPU와 PV mode를 확인한다.
RISC-V
- mechanism
- AMO aq/rl 또는 LR/SC와 Zacas capability
- state
- AMOADD가 fetch-add를 직접 수행하고
.aqrl이 full ordering variant를 만든다. cmpxchg는 LR/SC retry 또는 extension에 따라 다른 구현을 선택한다. - checkpoint
- AMO width, aq/rl, LR/SC retry, reservation granule와 ISA extension을 확인한다.
04 · SOURCE
Linux 6.18.37 원본 코드와 주요 구문 해설
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 원문 전체 발췌를 보존하고, 개별 해설은 근거가 있는 구문에만 붙였습니다.
arm64 · Linux 6.18.37
LSE atomic 또는 LL/SC alternative
LSE CPU는 LDADD/CAS 계열의 acquire/release suffix를 사용하고, 미지원 CPU는 load-exclusive/store-exclusive retry loop를 alternatives로 선택한다. qspinlock은 공통 algorithm 위에 이 primitive를 사용한다.
원본 코드: arch/arm64/include/asm/atomic.h:30-88
30ATOMIC_OP(atomic_sub)
31
32#undef ATOMIC_OP
33
34#define ATOMIC_FETCH_OP(name, op) \
35static __always_inline int arch_##op##name(int i, atomic_t *v) \
36{ \
37 return __lse_ll_sc_body(op##name, i, v); \
38}
39
40#define ATOMIC_FETCH_OPS(op) \
41 ATOMIC_FETCH_OP(_relaxed, op) \
42 ATOMIC_FETCH_OP(_acquire, op) \
43 ATOMIC_FETCH_OP(_release, op) \
44 ATOMIC_FETCH_OP( , op)
45
46ATOMIC_FETCH_OPS(atomic_fetch_andnot)
47ATOMIC_FETCH_OPS(atomic_fetch_or)
48ATOMIC_FETCH_OPS(atomic_fetch_xor)
49ATOMIC_FETCH_OPS(atomic_fetch_add)
50ATOMIC_FETCH_OPS(atomic_fetch_and)
51ATOMIC_FETCH_OPS(atomic_fetch_sub)
52ATOMIC_FETCH_OPS(atomic_add_return)
53ATOMIC_FETCH_OPS(atomic_sub_return)
54
55#undef ATOMIC_FETCH_OP
56#undef ATOMIC_FETCH_OPS
57
58#define ATOMIC64_OP(op) \
59static __always_inline void arch_##op(long i, atomic64_t *v) \
60{ \
61 __lse_ll_sc_body(op, i, v); \
62}
63
64ATOMIC64_OP(atomic64_andnot)
65ATOMIC64_OP(atomic64_or)
66ATOMIC64_OP(atomic64_xor)
67ATOMIC64_OP(atomic64_add)
68ATOMIC64_OP(atomic64_and)
69ATOMIC64_OP(atomic64_sub)
70
71#undef ATOMIC64_OP
72
73#define ATOMIC64_FETCH_OP(name, op) \
74static __always_inline long arch_##op##name(long i, atomic64_t *v) \
75{ \
76 return __lse_ll_sc_body(op##name, i, v); \
77}
78
79#define ATOMIC64_FETCH_OPS(op) \
80 ATOMIC64_FETCH_OP(_relaxed, op) \
81 ATOMIC64_FETCH_OP(_acquire, op) \
82 ATOMIC64_FETCH_OP(_release, op) \
83 ATOMIC64_FETCH_OP( , op)
84
85ATOMIC64_FETCH_OPS(atomic64_fetch_andnot)
86ATOMIC64_FETCH_OPS(atomic64_fetch_or)
87ATOMIC64_FETCH_OPS(atomic64_fetch_xor)
88ATOMIC64_FETCH_OPS(atomic64_fetch_add)주요 구문 해설
원본 59줄은 그대로 표시하며, 검토한 42개 구문에 설명을 붙였습니다.
ATOMIC_OP(atomic_sub)atomic_t에서 i를 원자적으로 빼고 반환값은 주지 않는 arch_atomic_sub 함수를 생성합니다. 실제 명령 경로는 LSE 지원에 따라 선택됩니다.
#undef ATOMIC_OP반환값 없는 32비트 함수 생성에 사용한 임시 ATOMIC_OP 매크로를 해제합니다. 이미 생성된 함수 정의는 남습니다.
#define ATOMIC_FETCH_OP(name, op) \반환값이 있는 32비트 원자 연산 함수를 만드는 틀입니다. op와 name을 이어 함수명과 순서 보장 종류를 정하고, __lse_ll_sc_body가 CPU에 맞는 LSE 또는 LL/SC 구현으로 넘깁니다. fetch 계열은 변경 전 값을, add_return·sub_return 계열은 변경 후 값을 돌려줍니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
static __always_inline int arch_##op##name(int i, atomic_t *v) \연산명 op와 순서 접미사 name을 붙인 32비트 atomic 함수를 정의합니다. i는 연산 값, v는 대상이며 int 반환값의 의미는 fetch 또는 return 연산 종류에 따라 달라집니다.
return __lse_ll_sc_body(op##name, i, v); \boot-time alternative가 LSE 또는 LL/SC 구현을 선택하는 공통 호출점이다.
#define ATOMIC_FETCH_OPS(op) \같은 32비트 연산에 relaxed·acquire·release·기본형 네 함수를 만듭니다. relaxed는 다른 메모리 접근의 순서를 추가로 보장하지 않고, acquire와 release는 각각 뒤와 앞의 접근을 연결하며, 기본형은 완전한 순서 보장을 제공하는 구현을 선택합니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
ATOMIC_FETCH_OP(_relaxed, op) \relaxed 변형을 생성합니다. 대상 값의 원자성은 보장하지만 앞뒤 일반 메모리 접근에 acquire/release 순서를 추가하지 않습니다.
ATOMIC_FETCH_OP(_acquire, op) \acquire 변형을 생성합니다. 이 원자적 읽기 뒤의 메모리 접근이 읽기보다 앞서 관찰되지 않게 하여 공개된 자료를 읽는 쪽에 사용합니다.
ATOMIC_FETCH_OP(_release, op) \release 변형을 생성합니다. 앞선 메모리 접근이 원자 갱신 뒤로 넘어가지 않게 하여 갱신과 함께 자료를 공개할 때 사용합니다.
ATOMIC_FETCH_OP( , op)접미사 없는 기본 변형을 생성합니다. Linux의 이 반환값 있는 atomic 연산은 완전한 메모리 순서 보장을 제공하는 기본 API입니다.
ATOMIC_FETCH_OPS(atomic_fetch_andnot)32비트 값에 i에서 1인 비트를 지우는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.
ATOMIC_FETCH_OPS(atomic_fetch_or)32비트 값에 i에서 1인 비트를 켜는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.
ATOMIC_FETCH_OPS(atomic_fetch_xor)32비트 값에 i에서 1인 비트를 반전하는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.
ATOMIC_FETCH_OPS(atomic_fetch_add)32비트 값에 i를 더하는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.
ATOMIC_FETCH_OPS(atomic_fetch_and)32비트 값에 i에서 0인 비트를 지우는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.
ATOMIC_FETCH_OPS(atomic_fetch_sub)32비트 값에 i를 빼는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.
ATOMIC_FETCH_OPS(atomic_add_return)relaxed/acquire/release/full 네 ordering variant를 생성한다.
ATOMIC_FETCH_OPS(atomic_sub_return)i를 뺀 뒤의 새 값을 반환하는 atomic_sub_return 계열 네 가지를 생성합니다. fetch_sub가 갱신 전 값을 주는 것과 반환 기준이 다릅니다.
#undef ATOMIC_FETCH_OP단일 반환형 atomic 함수를 만들던 임시 ATOMIC_FETCH_OP 매크로를 해제합니다. 다음 64비트 생성 매크로와 이름·범위를 분리합니다.
#undef ATOMIC_FETCH_OPS네 가지 순서 변형을 묶어 만들던 임시 매크로를 해제합니다. 생성된 32비트 함수들은 그대로 사용할 수 있습니다.
#define ATOMIC64_OP(op) \반환값이 없는 64비트 원자 연산 함수를 만듭니다. long 값 i와 atomic64_t를 받아 op에 맞는 LSE 또는 LL/SC 구현에서 카운터를 갱신하며, 갱신 전후 값을 호출자에게 돌려주지는 않습니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
static __always_inline void arch_##op(long i, atomic64_t *v) \반환값 없는 64비트 atomic 함수를 정의하는 매크로 본문입니다. long i를 atomic64_t 대상 v에 적용하며 op가 실제 연산 이름을 정합니다.
__lse_ll_sc_body(op, i, v); \LSE 원자 명령과 LL/SC 반복 중 CPU 기능에 맞는 op 구현을 선택하여 i와 v를 전달합니다. 이 64비트 함수는 결과값을 호출자에게 반환하지 않습니다.
ATOMIC64_OP(atomic64_andnot)64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i에서 1인 비트를 지웁니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.
ATOMIC64_OP(atomic64_or)64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i에서 1인 비트를 켭니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.
ATOMIC64_OP(atomic64_xor)64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i에서 1인 비트를 반전합니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.
ATOMIC64_OP(atomic64_add)64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i를 더합니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.
ATOMIC64_OP(atomic64_and)64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i와 비트별 AND를 합니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.
ATOMIC64_OP(atomic64_sub)64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i를 뺍니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.
#undef ATOMIC64_OP64비트 무반환 함수 생성이 끝나 임시 ATOMIC64_OP 매크로를 없앱니다. 컴파일된 atomic 함수가 삭제되는 것은 아닙니다.
#define ATOMIC64_FETCH_OP(name, op) \반환값이 있는 64비트 원자 연산 함수를 만듭니다. op와 순서 접미사 name을 합쳐 실제 연산을 선택하며 fetch는 변경 전 값, add_return·sub_return은 변경 후 값을 long으로 돌려줍니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
static __always_inline long arch_##op##name(long i, atomic64_t *v) \64비트 반환형 atomic 함수의 이름과 인자를 정의합니다. op와 name을 붙인 함수가 long 결과를 반환하며 i는 피연산자, v는 갱신 대상입니다.
return __lse_ll_sc_body(op##name, i, v); \boot-time alternative가 LSE 또는 LL/SC 구현을 선택하는 공통 호출점이다.
#define ATOMIC64_FETCH_OPS(op) \64비트 연산마다 relaxed·acquire·release·기본형을 함께 정의합니다. 데이터 폭은 모두 64비트이고, 다른 메모리 접근과 어떤 순서를 보장할지만 달라지므로 각 호출자가 필요한 순서 수준을 고를 수 있습니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
ATOMIC64_FETCH_OP(_relaxed, op) \64비트 연산의 relaxed 변형을 생성합니다. 대상 값의 원자적 갱신만 보장하고 주변 접근의 acquire/release 순서는 추가하지 않습니다.
ATOMIC64_FETCH_OP(_acquire, op) \64비트 연산의 acquire 변형을 생성합니다. 원자적 읽기 이후의 접근을 그 읽기보다 앞서지 않게 정렬합니다.
ATOMIC64_FETCH_OP(_release, op) \64비트 연산의 release 변형을 생성합니다. 앞서 준비한 데이터 접근이 원자적 갱신 이후로 넘어가지 않게 합니다.
ATOMIC64_FETCH_OP( , op)64비트 연산의 접미사 없는 기본 변형을 생성합니다. 이 반환형 atomic API의 완전한 메모리 순서 보장을 선택합니다.
ATOMIC64_FETCH_OPS(atomic64_fetch_andnot)64비트 대상에 i에서 1인 비트를 지우는 fetch 함수들을 생성합니다. 갱신 전 값을 반환하며 relaxed·acquire·release·기본 순서의 네 API를 제공합니다.
ATOMIC64_FETCH_OPS(atomic64_fetch_or)64비트 대상에 i에서 1인 비트를 켜는 fetch 함수들을 생성합니다. 갱신 전 값을 반환하며 relaxed·acquire·release·기본 순서의 네 API를 제공합니다.
ATOMIC64_FETCH_OPS(atomic64_fetch_xor)64비트 대상에 i에서 1인 비트를 반전하는 fetch 함수들을 생성합니다. 갱신 전 값을 반환하며 relaxed·acquire·release·기본 순서의 네 API를 제공합니다.
ATOMIC64_FETCH_OPS(atomic64_fetch_add)64비트 대상에 i를 더하는 fetch 함수들을 생성합니다. 갱신 전 값을 반환하며 relaxed·acquire·release·기본 순서의 네 API를 제공합니다.
x86-64 · Linux 6.18.37
LOCK prefix RMW와 CMPXCHG/XCHG
cache-coherent locked operation이 원자성과 강한 ordering을 제공한다. qspinlock은 fast cmpxchg 뒤 pending/tail queue를 사용하며 paravirt slow path가 대체될 수 있다.
원본 코드: arch/x86/include/asm/atomic.h:75-121
75#define arch_atomic_inc_and_test arch_atomic_inc_and_test
76
77static __always_inline bool arch_atomic_add_negative(int i, atomic_t *v)
78{
79 return GEN_BINARY_RMWcc(LOCK_PREFIX "addl", v->counter, s, "er", i);
80}
81#define arch_atomic_add_negative arch_atomic_add_negative
82
83static __always_inline int arch_atomic_add_return(int i, atomic_t *v)
84{
85 return i + xadd(&v->counter, i);
86}
87#define arch_atomic_add_return arch_atomic_add_return
88
89#define arch_atomic_sub_return(i, v) arch_atomic_add_return(-(i), v)
90
91static __always_inline int arch_atomic_fetch_add(int i, atomic_t *v)
92{
93 return xadd(&v->counter, i);
94}
95#define arch_atomic_fetch_add arch_atomic_fetch_add
96
97#define arch_atomic_fetch_sub(i, v) arch_atomic_fetch_add(-(i), v)
98
99static __always_inline int arch_atomic_cmpxchg(atomic_t *v, int old, int new)
100{
101 return arch_cmpxchg(&v->counter, old, new);
102}
103#define arch_atomic_cmpxchg arch_atomic_cmpxchg
104
105static __always_inline bool arch_atomic_try_cmpxchg(atomic_t *v, int *old, int new)
106{
107 return arch_try_cmpxchg(&v->counter, old, new);
108}
109#define arch_atomic_try_cmpxchg arch_atomic_try_cmpxchg
110
111static __always_inline int arch_atomic_xchg(atomic_t *v, int new)
112{
113 return arch_xchg(&v->counter, new);
114}
115#define arch_atomic_xchg arch_atomic_xchg
116
117static __always_inline void arch_atomic_and(int i, atomic_t *v)
118{
119 asm_inline volatile(LOCK_PREFIX "andl %1, %0"
120 : "+m" (v->counter)
121 : "ir" (i)주요 구문 해설
원본 47줄은 그대로 표시하며, 검토한 25개 구문에 설명을 붙였습니다.
#define arch_atomic_inc_and_test arch_atomic_inc_and_testarch_atomic_inc_and_test이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 32비트 카운터를 1 늘린 결과가 0인지 반환합니다.
static __always_inline bool arch_atomic_add_negative(int i, atomic_t *v)v에 i를 원자적으로 더하고 결과가 음수인지 bool로 반환하는 함수입니다. 카운터 값 자체 대신 덧셈 결과의 부호를 필요로 할 때 사용합니다.
return GEN_BINARY_RMWcc(LOCK_PREFIX "addl", v->counter, s, "er", i);LOCK이 붙은 32비트 덧셈으로 counter를 갱신하고 조건 코드 s로 부호 플래그를 결과에 담습니다. 반환값은 이전 값이나 합계가 아니라 합계가 음수인지 여부입니다.
#define arch_atomic_add_negative arch_atomic_add_negativearch_atomic_add_negative이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 32비트 카운터에 i를 더한 결과가 음수인지 반환합니다.
static __always_inline int arch_atomic_add_return(int i, atomic_t *v)i를 v에 원자적으로 더한 뒤 새 합계를 반환하는 함수입니다. 내부 xadd가 주는 이전 값에 i를 더해 반환값을 만듭니다.
return i + xadd(&v->counter, i);XADD가 old value를 반환하므로 operand i와 합쳐 new value를 만든다.
#define arch_atomic_add_return arch_atomic_add_returnarch_atomic_add_return이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 32비트 카운터에 i를 더한 뒤의 새 값을 반환합니다.
#define arch_atomic_sub_return(i, v) arch_atomic_add_return(-(i), v)i를 빼는 연산을 -i를 더하는 arch_atomic_add_return으로 구현합니다. 같은 원자적 덧셈 경로를 재사용하되 반환값은 감산이 끝난 뒤의 카운터 값입니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
static __always_inline int arch_atomic_fetch_add(int i, atomic_t *v)i를 원자적으로 더하되 갱신 전 값을 반환하는 fetch_add 함수입니다. 여러 CPU가 같은 카운터에서 서로 다른 이전 값을 확보할 때 사용합니다.
return xadd(&v->counter, i);xadd로 counter에 i를 더하고 덧셈 전 값을 반환합니다. 단순 load와 add로 나누지 않아 경쟁 중 갱신을 잃지 않습니다.
#define arch_atomic_fetch_add arch_atomic_fetch_addarch_atomic_fetch_add이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 32비트 카운터에 i를 더하면서 변경 전 값을 반환합니다.
#define arch_atomic_fetch_sub(i, v) arch_atomic_fetch_add(-(i), v)i를 빼는 연산을 -i를 더하는 arch_atomic_fetch_add로 바꿉니다. fetch 계열이므로 반환값은 감산 결과가 아니라 갱신 전 카운터 값입니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
static __always_inline int arch_atomic_cmpxchg(atomic_t *v, int old, int new)v가 old와 같을 때만 new로 바꾸고 실제 이전 값을 반환하는 비교 교환 함수입니다. 반환값과 old를 비교하면 교환 성공 여부를 알 수 있습니다.
return arch_cmpxchg(&v->counter, old, new);expected value가 일치할 때만 한 cache-line transaction으로 교체한다.
#define arch_atomic_cmpxchg arch_atomic_cmpxchgarch_atomic_cmpxchg이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 카운터가 예상값 old와 같을 때만 new로 바꾸고 관찰한 이전 값을 반환합니다.
static __always_inline bool arch_atomic_try_cmpxchg(atomic_t *v, int *old, int new)v가 *old와 같으면 new로 바꾸고 성공 여부를 bool로 반환합니다. 실패하면 관측한 실제 값을 *old에 써 다음 시도의 비교값으로 사용할 수 있게 합니다.
return arch_try_cmpxchg(&v->counter, old, new);counter 주소와 old 포인터, new 값을 x86 비교 교환 구현에 전달합니다. 성공 여부를 반환하고 실패 시 기대값 포인터를 실제 값으로 갱신하는 API를 그대로 제공합니다.
#define arch_atomic_try_cmpxchg arch_atomic_try_cmpxchgarch_atomic_try_cmpxchg이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 예상값과 일치할 때 new로 바꾸어 성공 여부를 반환하고 실패하면 *old에 관찰값을 기록합니다.
static __always_inline int arch_atomic_xchg(atomic_t *v, int new)counter를 무조건 new로 원자 교환하고 교환 전 값을 반환하는 함수입니다. 기대값을 비교하는 cmpxchg와 달리 일치 조건이 없습니다.
return arch_xchg(&v->counter, new);counter에 대한 x86 원자 교환을 실행하고 이전 값을 반환합니다. 메모리 피연산자의 XCHG를 사용하여 다른 CPU와의 교환도 원자적으로 처리합니다.
#define arch_atomic_xchg arch_atomic_xchgarch_atomic_xchg이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 카운터를 new로 원자적으로 바꾸고 이전 값을 반환합니다.
static __always_inline void arch_atomic_and(int i, atomic_t *v)v->counter와 i의 비트별 AND를 원자적으로 수행하는 함수입니다. i에서 0인 비트를 지우며 별도 결과값은 반환하지 않습니다.
asm_inline volatile(LOCK_PREFIX "andl %1, %0"32비트 메모리 AND 명령에 LOCK 접두사를 적용하여 원자 갱신을 수행합니다. volatile은 컴파일러가 이 어셈블리를 불필요하다고 없애지 못하게 합니다.
: "+m" (v->counter)+m은 counter 메모리를 입력으로 읽고 출력으로 다시 쓴다는 제약입니다. 컴파일러에 이 명령의 실제 갱신 대상을 알립니다.
: "ir" (i)AND 마스크 i를 즉시값 또는 레지스터 피연산자로 전달합니다. ir 제약으로 컴파일러가 값에 맞는 명령 피연산자 형태를 고를 수 있습니다.
RISC-V · Linux 6.18.37
AMO aq/rl 또는 LR/SC와 Zacas capability
AMOADD가 fetch-add를 직접 수행하고 .aqrl이 full ordering variant를 만든다. cmpxchg는 LR/SC retry 또는 extension에 따라 다른 구현을 선택한다.
원본 코드: arch/riscv/include/asm/atomic.h:92-166
92 register c_type ret; \
93 __asm__ __volatile__ ( \
94 " amo" #asm_op "." #asm_type " %1, %2, %0" \
95 : "+A" (v->counter), "=r" (ret) \
96 : "r" (I) \
97 : "memory"); \
98 return ret; \
99} \
100static __always_inline \
101c_type arch_atomic##prefix##_fetch_##op(c_type i, atomic##prefix##_t *v) \
102{ \
103 register c_type ret; \
104 __asm__ __volatile__ ( \
105 " amo" #asm_op "." #asm_type ".aqrl %1, %2, %0" \
106 : "+A" (v->counter), "=r" (ret) \
107 : "r" (I) \
108 : "memory"); \
109 return ret; \
110}
111
112#define ATOMIC_OP_RETURN(op, asm_op, c_op, I, asm_type, c_type, prefix) \
113static __always_inline \
114c_type arch_atomic##prefix##_##op##_return_relaxed(c_type i, \
115 atomic##prefix##_t *v) \
116{ \
117 return arch_atomic##prefix##_fetch_##op##_relaxed(i, v) c_op I; \
118} \
119static __always_inline \
120c_type arch_atomic##prefix##_##op##_return(c_type i, atomic##prefix##_t *v) \
121{ \
122 return arch_atomic##prefix##_fetch_##op(i, v) c_op I; \
123}
124
125#ifdef CONFIG_GENERIC_ATOMIC64
126#define ATOMIC_OPS(op, asm_op, c_op, I) \
127 ATOMIC_FETCH_OP( op, asm_op, I, w, int, ) \
128 ATOMIC_OP_RETURN(op, asm_op, c_op, I, w, int, )
129#else
130#define ATOMIC_OPS(op, asm_op, c_op, I) \
131 ATOMIC_FETCH_OP( op, asm_op, I, w, int, ) \
132 ATOMIC_OP_RETURN(op, asm_op, c_op, I, w, int, ) \
133 ATOMIC_FETCH_OP( op, asm_op, I, d, s64, 64) \
134 ATOMIC_OP_RETURN(op, asm_op, c_op, I, d, s64, 64)
135#endif
136
137ATOMIC_OPS(add, add, +, i)
138ATOMIC_OPS(sub, add, +, -i)
139
140#define arch_atomic_add_return_relaxed arch_atomic_add_return_relaxed
141#define arch_atomic_sub_return_relaxed arch_atomic_sub_return_relaxed
142#define arch_atomic_add_return arch_atomic_add_return
143#define arch_atomic_sub_return arch_atomic_sub_return
144
145#define arch_atomic_fetch_add_relaxed arch_atomic_fetch_add_relaxed
146#define arch_atomic_fetch_sub_relaxed arch_atomic_fetch_sub_relaxed
147#define arch_atomic_fetch_add arch_atomic_fetch_add
148#define arch_atomic_fetch_sub arch_atomic_fetch_sub
149
150#ifndef CONFIG_GENERIC_ATOMIC64
151#define arch_atomic64_add_return_relaxed arch_atomic64_add_return_relaxed
152#define arch_atomic64_sub_return_relaxed arch_atomic64_sub_return_relaxed
153#define arch_atomic64_add_return arch_atomic64_add_return
154#define arch_atomic64_sub_return arch_atomic64_sub_return
155
156#define arch_atomic64_fetch_add_relaxed arch_atomic64_fetch_add_relaxed
157#define arch_atomic64_fetch_sub_relaxed arch_atomic64_fetch_sub_relaxed
158#define arch_atomic64_fetch_add arch_atomic64_fetch_add
159#define arch_atomic64_fetch_sub arch_atomic64_fetch_sub
160#endif
161
162#undef ATOMIC_OPS
163
164#ifdef CONFIG_GENERIC_ATOMIC64
165#define ATOMIC_OPS(op, asm_op, I) \
166 ATOMIC_FETCH_OP(op, asm_op, I, w, int, )주요 구문 해설
원본 75줄은 그대로 표시하며, 검토한 59개 구문에 설명을 붙였습니다.
register c_type ret; \원자적 연산 전의 값을 받을 ret 변수를 선언합니다. c_type은 생성할 atomic 함수의 정수형이며, register는 컴파일러에 주는 저장 방식 힌트입니다. 반환 명령이나 권한 전환을 실행하는 줄이 아닙니다.
__asm__ __volatile__ ( \갱신 전 값을 돌려주는 relaxed AMO 명령의 인라인 어셈블리 구간을 시작합니다. volatile은 이 원자 연산을 임의로 제거하지 못하게 합니다.
" amo" #asm_op "." #asm_type " %1, %2, %0" \AMO instruction이 memory word와 register 결과를 한 원자적 transaction으로 갱신한다.
: "+A" (v->counter), "=r" (ret) \인라인 어셈블리의 출력 조건을 지정합니다. +A는 v->counter 메모리를 읽고 갱신한다는 뜻이고, =r은 AMO가 읽어 온 갱신 전 값을 레지스터 출력 ret에 받겠다는 뜻입니다.
: "r" (I) \AMO에 더하거나 비트 연산할 값 I를 일반 레지스터 입력으로 전달합니다. 뺄셈 변형에서는 I가 -i로 확장되어 덧셈 명령으로 뺄셈을 구현합니다.
: "memory"); \memory clobber로 컴파일러에 메모리 영향이 있음을 알리고 어셈블리를 마칩니다. 이 표기만으로 하드웨어 acquire/release 순서가 추가되는 것은 아닙니다.
return ret; \AMO로 갱신하기 전의 값을 호출자에게 돌려줍니다. fetch 계열 atomic 함수의 반환 규칙이며, 이 relaxed 버전은 연산 앞뒤의 일반 메모리 접근에 acquire/release 순서를 추가하지 않습니다.
static __always_inline \기본 순서 fetch 함수에 내부 연결과 강제 인라인 속성을 붙입니다. 실제 타입·함수명·인자는 다음 줄의 매크로 조합으로 완성됩니다.
c_type arch_atomic##prefix##_fetch_##op(c_type i, atomic##prefix##_t *v) \prefix와 op를 붙여 32비트 또는 64비트 fetch 함수 이름을 만듭니다. i를 대상 v에 적용하고 갱신 전 값을 c_type으로 반환합니다.
register c_type ret; \순서 보장이 있는 fetch 연산에서 갱신 전 값을 받을 ret 변수를 선언합니다. c_type은 매크로 인자로 정하는 자료형이며, 이 선언만으로 레지스터나 실행 문맥을 복원하지 않습니다.
__asm__ __volatile__ ( \완전한 순서 보장을 가진 fetch AMO의 어셈블리를 시작합니다. 바로 뒤 명령의 .aqrl이 acquire와 release를 함께 부여합니다.
" amo" #asm_op "." #asm_type ".aqrl %1, %2, %0" \AMO instruction이 memory word와 register 결과를 한 원자적 transaction으로 갱신한다.
: "+A" (v->counter), "=r" (ret) \v->counter를 읽고 쓰는 메모리 피연산자로, ret를 레지스터 출력으로 지정합니다. 앞의 .aqrl AMO 명령이 원자 갱신과 메모리 순서 보장을 수행하고 ret에는 갱신 전 값을 남깁니다.
: "r" (I) \순서 보장형 AMO의 연산 값 I를 레지스터로 전달합니다. 주소 입력과 결과 출력은 앞줄에, 실제 산술·비트 연산 피연산자는 여기에 명시합니다.
: "memory"); \어셈블리가 메모리에 영향을 준다는 compiler clobber를 선언합니다. 하드웨어 순서는 앞의 .aqrl이 맡고 컴파일러의 재배치도 이 경계에서 제한합니다.
return ret; \원자 연산으로 바꾸기 전의 counter 값을 반환합니다. acquire/release 순서 보장은 앞의 .aqrl 명령에서 이루어졌으며, 이 줄은 그 결과를 C 호출자에게 전달합니다.
#define ATOMIC_OP_RETURN(op, asm_op, c_op, I, asm_type, c_type, prefix) \원자 연산 뒤의 새 값을 반환하는 함수를 만듭니다. AMO fetch가 돌려준 변경 전 값에 c_op와 I를 적용해 새 값을 계산합니다. asm_type·c_type·prefix로 폭을 고르며, 아래 sub 생성은 add 연산에 -i를 넣어 감산을 구현합니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
static __always_inline \갱신 후 값을 반환하는 relaxed 함수의 내부 연결·인라인 속성을 지정합니다. 아래의 fetch 연산 결과에서 새 값을 계산하는 작은 함수를 호출 지점에 펼칩니다.
c_type arch_atomic##prefix##_##op##_return_relaxed(c_type i, \op_return_relaxed 이름의 반환형 원자 함수를 정의하기 시작합니다. i가 갱신량이며 반환값은 연산 전 값이 아니라 연산 후 값입니다.
atomic##prefix##_t *v) \원자 변수 포인터 v를 두 번째 인자로 받으며 함수 정의를 마칩니다. prefix에 따라 atomic_t 또는 atomic64_t가 선택됩니다.
return arch_atomic##prefix##_fetch_##op##_relaxed(i, v) c_op I; \relaxed fetch 연산이 돌려준 이전 값에 c_op와 I를 적용해 갱신 후 값을 반환합니다. 메모리는 fetch에서 한 번만 원자 갱신하며 뒤의 계산은 반환값을 만드는 계산입니다.
static __always_inline \기본 순서의 갱신 후 값 반환 함수에 내부 연결과 인라인 속성을 부여합니다. 함수 호출 비용 없이 뒤의 원자 연산을 사용할 수 있게 합니다.
c_type arch_atomic##prefix##_##op##_return(c_type i, atomic##prefix##_t *v) \op_return 함수의 이름·정수형·대상 포인터를 생성합니다. relaxed 접미사가 없으므로 아래의 기본 순서 fetch 연산을 사용합니다.
return arch_atomic##prefix##_fetch_##op(i, v) c_op I; \순서 보장형 fetch가 준 이전 값으로 새 값을 계산해 반환합니다. 실제 원자 갱신과 .aqrl 순서 보장은 호출한 fetch 함수에 들어 있습니다.
#ifdef CONFIG_GENERIC_ATOMIC64GENERIC_ATOMIC64를 사용하는 빌드는 64비트 원자 연산을 공통 구현에 맡깁니다. 이 분기는 여기서 32비트 AMO 함수만 생성하며, 공통 구현을 쓰지 않는 분기에서는 64비트 AMO 함수도 함께 생성합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#define ATOMIC_OPS(op, asm_op, c_op, I) \CONFIG_GENERIC_ATOMIC64 구성에서는 이 틀이 32비트 AMO의 fetch형과 변경 후 값 반환형만 만듭니다. 64비트 원자 연산은 별도의 공통 구현이 담당하므로 여기서 64비트 AMO 함수를 중복 생성하지 않습니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
ATOMIC_FETCH_OP( op, asm_op, I, w, int, ) \32비트 AMO.W와 int를 사용하는 fetch 함수들을 생성합니다. GENERIC_ATOMIC64 구성에서는 64비트 원자 연산을 공통 구현에 맡겨 여기서 생성하지 않습니다.
ATOMIC_OP_RETURN(op, asm_op, c_op, I, w, int, )32비트 fetch 결과에서 갱신 후 값을 계산하는 return 함수들을 생성합니다. 메모리 대상은 atomic_t이며 64비트형은 공통 atomic64 구현이 담당합니다.
#else공통 atomic64 구현을 쓰지 않는 빌드에서는 AMO word 연산의 32비트 함수와 AMO doubleword 연산의 64비트 함수를 모두 생성합니다. 두 정수 폭에 맞는 구현을 아키텍처가 직접 제공합니다.
#define ATOMIC_OPS(op, asm_op, c_op, I) \64비트 원자 연산을 공통 대체 구현에 맡기지 않는 구성의 틀입니다. w/int 조합으로 32비트, d/s64/64 조합으로 64비트 AMO 함수를 만들며 각 폭에 fetch형과 변경 후 값 반환형을 모두 제공합니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
ATOMIC_FETCH_OP( op, asm_op, I, w, int, ) \하드웨어 64비트 atomic을 사용하는 구성에서도 기본 32비트 fetch 함수를 함께 생성합니다. w와 int가 명령 폭과 C 자료형을 정합니다.
ATOMIC_OP_RETURN(op, asm_op, c_op, I, w, int, ) \32비트 atomic의 갱신 후 값 반환 함수를 생성합니다. 이어지는 64비트 생성과 구분하여 prefix 없이 정의합니다.
ATOMIC_FETCH_OP( op, asm_op, I, d, s64, 64) \AMO.D와 s64를 사용해 64비트 fetch 함수들을 생성합니다. prefix 64가 함수명과 atomic64_t 형에 반영됩니다.
ATOMIC_OP_RETURN(op, asm_op, c_op, I, d, s64, 64)64비트 fetch가 돌려준 이전 값에서 새 값을 계산하는 return 함수들을 생성합니다. 64비트 정수 연산으로 결과 폭을 유지합니다.
#endif125행에서 시작한 선택 구간을 마칩니다. GENERIC_ATOMIC64를 사용하는 빌드는 64비트 원자 연산을 공통 구현에 맡깁니다. 이 분기는 여기서 32비트 AMO 함수만 생성하며, 공통 구현을 쓰지 않는 분기에서는 64비트 AMO 함수도 함께 생성합니다.
ATOMIC_OPS(add, add, +, i)32/64-bit add의 relaxed와 ordered variant를 macro로 생성한다.
ATOMIC_OPS(sub, add, +, -i)sub API를 AMO ADD와 -i 조합으로 생성합니다. 이전 값에 -i를 더하는 방식으로 원자 뺄셈을 구현하고, return 변형도 같은 계산으로 새 값을 만듭니다.
#define arch_atomic_add_return_relaxed arch_atomic_add_return_relaxed위의 생성 매크로가 만든 arch_atomic_add_return_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 더하고 변경 후 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.
#define arch_atomic_sub_return_relaxed arch_atomic_sub_return_relaxed위의 생성 매크로가 만든 arch_atomic_sub_return_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 빼고 변경 후 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.
#define arch_atomic_add_return arch_atomic_add_return위의 생성 매크로가 만든 arch_atomic_add_return을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 더하고 변경 후 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.
#define arch_atomic_sub_return arch_atomic_sub_return위의 생성 매크로가 만든 arch_atomic_sub_return을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 빼고 변경 후 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.
#define arch_atomic_fetch_add_relaxed arch_atomic_fetch_add_relaxed위의 생성 매크로가 만든 arch_atomic_fetch_add_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 더하고 변경 전 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.
#define arch_atomic_fetch_sub_relaxed arch_atomic_fetch_sub_relaxed위의 생성 매크로가 만든 arch_atomic_fetch_sub_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 빼고 변경 전 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.
#define arch_atomic_fetch_add arch_atomic_fetch_add위의 생성 매크로가 만든 arch_atomic_fetch_add을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 더하고 변경 전 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.
#define arch_atomic_fetch_sub arch_atomic_fetch_sub위의 생성 매크로가 만든 arch_atomic_fetch_sub을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 빼고 변경 전 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.
#ifndef CONFIG_GENERIC_ATOMIC6464비트 원자 연산을 공통 구현에 맡기지 않는 경우에만 아키텍처의 atomic64 함수 이름들을 공개합니다. 공통 구현과 같은 이름의 64비트 연산이 중복 정의되지 않게 하는 선택입니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#define arch_atomic64_add_return_relaxed arch_atomic64_add_return_relaxed위의 생성 매크로가 만든 arch_atomic64_add_return_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 더하고 변경 후 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.
#define arch_atomic64_sub_return_relaxed arch_atomic64_sub_return_relaxed위의 생성 매크로가 만든 arch_atomic64_sub_return_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 빼고 변경 후 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.
#define arch_atomic64_add_return arch_atomic64_add_return위의 생성 매크로가 만든 arch_atomic64_add_return을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 더하고 변경 후 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.
#define arch_atomic64_sub_return arch_atomic64_sub_return위의 생성 매크로가 만든 arch_atomic64_sub_return을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 빼고 변경 후 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.
#define arch_atomic64_fetch_add_relaxed arch_atomic64_fetch_add_relaxed위의 생성 매크로가 만든 arch_atomic64_fetch_add_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 더하고 변경 전 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.
#define arch_atomic64_fetch_sub_relaxed arch_atomic64_fetch_sub_relaxed위의 생성 매크로가 만든 arch_atomic64_fetch_sub_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 빼고 변경 전 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.
#define arch_atomic64_fetch_add arch_atomic64_fetch_add위의 생성 매크로가 만든 arch_atomic64_fetch_add을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 더하고 변경 전 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.
#define arch_atomic64_fetch_sub arch_atomic64_fetch_sub위의 생성 매크로가 만든 arch_atomic64_fetch_sub을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 빼고 변경 전 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.
#endif150행에서 시작한 선택 구간을 마칩니다. 64비트 원자 연산을 공통 구현에 맡기지 않는 경우에만 아키텍처의 atomic64 함수 이름들을 공개합니다. 공통 구현과 같은 이름의 64비트 연산이 중복 정의되지 않게 하는 선택입니다.
#undef ATOMIC_OPS덧셈·뺄셈용 ATOMIC_OPS 매크로를 해제합니다. 아래에서 비트 연산에 필요한 다른 인자 목록으로 같은 이름을 다시 정의합니다.
#ifdef CONFIG_GENERIC_ATOMIC6464비트 원자 연산에 공통 구현을 쓰는 경우 아래 연산 생성 매크로는 32비트 함수만 만듭니다. 반대 분기는 doubleword AMO를 사용하는 64비트 함수까지 생성합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#define ATOMIC_OPS(op, asm_op, I) \이 지점에서는 논리 연산용으로 ATOMIC_OPS를 다시 정의합니다. CONFIG_GENERIC_ATOMIC64 구성이므로 and·or·xor의 32비트 fetch형만 만들고, 64비트 연산은 공통 구현에 맡깁니다. 앞의 산술용 틀과 달리 변경 후 값을 계산하는 OP_RETURN은 만들지 않습니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.
ATOMIC_FETCH_OP(op, asm_op, I, w, int, )32비트 비트 연산용 fetch 함수들을 생성합니다. 비트 연산에는 갱신 후 값을 주는 별도 return 계열을 만들지 않으며 64비트는 GENERIC_ATOMIC64 경로에 맡깁니다.
05 · WORKED EXAMPLE
숫자로 검산하기
contended atomic의 cache-line 이동 비용
4 CPU가 서로 다른 socket/hart cluster에서 같은 counter를 1,000,000회 증가시키고 line ownership 이동이 평균 120ns라고 가정한다.
- serialization한 cache line의 RMW는 사실상 ownership을 직렬화한다.
- lower bound1,000,000 x 120ns = 120ms가 coherence 이동만의 하한이다.
- LL/SCretry가 평균 1.4회면 load/store-exclusive 시도 수는 1.4M으로 늘어난다.
- alternativeper-CPU counter 후 합산하면 hot path line bouncing을 제거하고 read 시 aggregation 비용을 낸다.
결론atomic instruction 하나의 cycle이 아니라 contention과 cache topology를 포함해 upper bound를 잡는다.
06 · DEEP DIVE
경계별 상세 분석
공통 kernel core와 architecture hook의 경계
원자적 read-modify-write와 memory order는 별개 축이다. relaxed는 tear/lost-update를 막지만 주변 일반 load/store의 order를 전부 보장하지 않는다. lock slow path는 contention queue와 fairness를 추가한다.
atomic variable의 cache line, retry/queue state와 critical section data를 구분한다. lock acquire 이전과 release 이후 access가 compiler와 CPU 양쪽에서 이동하지 않게 해야 한다.
arm64: LSE atomic 또는 LL/SC alternative
LSE CPU는 LDADD/CAS 계열의 acquire/release suffix를 사용하고, 미지원 CPU는 load-exclusive/store-exclusive retry loop를 alternatives로 선택한다. qspinlock은 공통 algorithm 위에 이 primitive를 사용한다.
LL/SC loop 실패와 barrier variant가 return value publication 순서를 결정한다. 디버깅할 때는 LSE capability, selected alternative, retry count, cache line owner와 acquire/release suffix를 본다.
x86-64: LOCK prefix RMW와 CMPXCHG/XCHG
cache-coherent locked operation이 원자성과 강한 ordering을 제공한다. qspinlock은 fast cmpxchg 뒤 pending/tail queue를 사용하며 paravirt slow path가 대체될 수 있다.
TSO보다 강한 locked op를 단순 load/store와 구분하고 compiler memory clobber를 확인한다. 디버깅할 때는 LOCK instruction, cache line, qspinlock val, pending/tail, holder CPU와 PV mode를 확인한다.
RISC-V: AMO aq/rl 또는 LR/SC와 Zacas capability
AMOADD가 fetch-add를 직접 수행하고 .aqrl이 full ordering variant를 만든다. cmpxchg는 LR/SC retry 또는 extension에 따라 다른 구현을 선택한다.
RVWMO에서는 aq/rl가 없는 relaxed AMO 주변의 일반 memory order를 별도 fence가 보완해야 한다. 디버깅할 때는 AMO width, aq/rl, LR/SC retry, reservation granule와 ISA extension을 확인한다.
객체 수명과 소유권을 먼저 고정한다
lock word와 보호 객체는 모든 waiter가 queue에서 빠질 때까지 살아 있어야 한다. lock을 포함한 object를 unlock 직후 free하면 다음 waiter가 재사용 memory를 lock으로 해석할 수 있다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
latency upper bound는 hardware instruction 하나가 아니다
uncontended RMW, cache-line transfer, LL/SC retry, qspinlock slow path와 virtualization paravirt hook을 분리한다. worst case는 lock holder preemption과 NUMA line bouncing이 결정한다.
평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | exclusive reservation이 잦게 깨지거나 잘못된 relaxed variant를 사용해 protected data가 늦게 보인다. | LSE capability, selected alternative, retry count, cache line owner와 acquire/release suffix를 본다. |
| x86-64 | false sharing으로 unrelated atomic이 같은 line을 왕복하거나 PV hook 불일치로 lockup이 난다. | LOCK instruction, cache line, qspinlock val, pending/tail, holder CPU와 PV mode를 확인한다. |
| RISC-V | reservation granule contention이나 misaligned atomic이 progress/fault 문제를 만든다. | AMO width, aq/rl, LR/SC retry, reservation granule와 ISA extension을 확인한다. |
08 · LAB
재현과 계측 절차
- 같은 atomic을 relaxed와 full variant로 빌드해 disassembly와 litmus outcome을 비교한다.
- lockstat/perf c2c로 qspinlock slow path와 false sharing cache line을 찾는다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/include/asm/atomic.h:30-88
- x86-64arch/x86/include/asm/atomic.h:75-121
- RISC-Varch/riscv/include/asm/atomic.h:92-166
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.