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에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.
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개 줄에 각각 설명을 붙였습니다.
ATOMIC_OP(atomic_sub)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#undef ATOMIC_OP이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define ATOMIC_FETCH_OP(name, op) \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
static __always_inline int arch_##op##name(int i, atomic_t *v) \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{ \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
return __lse_ll_sc_body(op##name, i, v); \boot-time alternative가 LSE 또는 LL/SC 구현을 선택하는 공통 호출점이다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define ATOMIC_FETCH_OPS(op) \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
ATOMIC_FETCH_OP(_relaxed, op) \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC_FETCH_OP(_acquire, op) \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC_FETCH_OP(_release, op) \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC_FETCH_OP( , op)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
ATOMIC_FETCH_OPS(atomic_fetch_andnot)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC_FETCH_OPS(atomic_fetch_or)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC_FETCH_OPS(atomic_fetch_xor)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC_FETCH_OPS(atomic_fetch_add)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC_FETCH_OPS(atomic_fetch_and)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC_FETCH_OPS(atomic_fetch_sub)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC_FETCH_OPS(atomic_add_return)relaxed/acquire/release/full 네 ordering variant를 생성한다.
ATOMIC_FETCH_OPS(atomic_sub_return)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#undef ATOMIC_FETCH_OP이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
#undef ATOMIC_FETCH_OPS이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define ATOMIC64_OP(op) \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
static __always_inline void arch_##op(long i, atomic64_t *v) \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{ \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
__lse_ll_sc_body(op, i, v); \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
ATOMIC64_OP(atomic64_andnot)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC64_OP(atomic64_or)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC64_OP(atomic64_xor)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC64_OP(atomic64_add)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC64_OP(atomic64_and)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC64_OP(atomic64_sub)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#undef ATOMIC64_OP이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define ATOMIC64_FETCH_OP(name, op) \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
static __always_inline long arch_##op##name(long i, atomic64_t *v) \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{ \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
return __lse_ll_sc_body(op##name, i, v); \boot-time alternative가 LSE 또는 LL/SC 구현을 선택하는 공통 호출점이다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define ATOMIC64_FETCH_OPS(op) \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
ATOMIC64_FETCH_OP(_relaxed, op) \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC64_FETCH_OP(_acquire, op) \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC64_FETCH_OP(_release, op) \이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC64_FETCH_OP( , op)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
(blank)빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
ATOMIC64_FETCH_OPS(atomic64_fetch_andnot)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC64_FETCH_OPS(atomic64_fetch_or)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC64_FETCH_OPS(atomic64_fetch_xor)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
ATOMIC64_FETCH_OPS(atomic64_fetch_add)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
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개 줄에 각각 설명을 붙였습니다.
#define arch_atomic_inc_and_test arch_atomic_inc_and_testcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static __always_inline bool arch_atomic_add_negative(int i, atomic_t *v)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return GEN_BINARY_RMWcc(LOCK_PREFIX "addl", v->counter, s, "er", i);이 함수가 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#define arch_atomic_add_negative arch_atomic_add_negativecompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static __always_inline int arch_atomic_add_return(int i, atomic_t *v)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return i + xadd(&v->counter, i);XADD가 old value를 반환하므로 operand i와 합쳐 new value를 만든다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#define arch_atomic_add_return arch_atomic_add_returncompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define arch_atomic_sub_return(i, v) arch_atomic_add_return(-(i), v)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static __always_inline int arch_atomic_fetch_add(int i, atomic_t *v)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return xadd(&v->counter, i);원자적 갱신 또는 exclusive transaction에 참여한다. 성공 값뿐 아니라 acquire/release ordering과 retry 조건을 앞뒤 줄에서 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#define arch_atomic_fetch_add arch_atomic_fetch_addcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define arch_atomic_fetch_sub(i, v) arch_atomic_fetch_add(-(i), v)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static __always_inline int arch_atomic_cmpxchg(atomic_t *v, int old, int new)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return arch_cmpxchg(&v->counter, old, new);expected value가 일치할 때만 한 cache-line transaction으로 교체한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#define arch_atomic_cmpxchg arch_atomic_cmpxchgcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static __always_inline bool arch_atomic_try_cmpxchg(atomic_t *v, int *old, int new)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return arch_try_cmpxchg(&v->counter, old, new);이 함수가 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#define arch_atomic_try_cmpxchg arch_atomic_try_cmpxchgcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static __always_inline int arch_atomic_xchg(atomic_t *v, int new)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return arch_xchg(&v->counter, new);이 함수가 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#define arch_atomic_xchg arch_atomic_xchgcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static __always_inline void arch_atomic_and(int i, atomic_t *v)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
asm_inline volatile(LOCK_PREFIX "andl %1, %0"이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
: "+m" (v->counter)이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
: "ir" (i)이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
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개 줄에 각각 설명을 붙였습니다.
register c_type ret; \저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
__asm__ __volatile__ ( \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
" amo" #asm_op "." #asm_type " %1, %2, %0" \AMO instruction이 memory word와 register 결과를 한 원자적 transaction으로 갱신한다.
: "+A" (v->counter), "=r" (ret) \저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
: "r" (I) \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
: "memory"); \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
return ret; \저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
} \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
static __always_inline \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
c_type arch_atomic##prefix##_fetch_##op(c_type i, atomic##prefix##_t *v) \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{ \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
register c_type ret; \저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
__asm__ __volatile__ ( \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
" amo" #asm_op "." #asm_type ".aqrl %1, %2, %0" \AMO instruction이 memory word와 register 결과를 한 원자적 transaction으로 갱신한다.
: "+A" (v->counter), "=r" (ret) \저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
: "r" (I) \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
: "memory"); \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
return ret; \저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define ATOMIC_OP_RETURN(op, asm_op, c_op, I, asm_type, c_type, prefix) \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
static __always_inline \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
c_type arch_atomic##prefix##_##op##_return_relaxed(c_type i, \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
atomic##prefix##_t *v) \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{ \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
return arch_atomic##prefix##_fetch_##op##_relaxed(i, v) c_op I; \이 함수가 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
} \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
static __always_inline \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
c_type arch_atomic##prefix##_##op##_return(c_type i, atomic##prefix##_t *v) \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{ \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
return arch_atomic##prefix##_fetch_##op(i, v) c_op I; \이 함수가 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_GENERIC_ATOMIC64Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define ATOMIC_OPS(op, asm_op, c_op, I) \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
ATOMIC_FETCH_OP( op, asm_op, I, w, int, ) \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC_OP_RETURN(op, asm_op, c_op, I, w, int, )이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
#elseKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define ATOMIC_OPS(op, asm_op, c_op, I) \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
ATOMIC_FETCH_OP( op, asm_op, I, w, int, ) \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC_OP_RETURN(op, asm_op, c_op, I, w, int, ) \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC_FETCH_OP( op, asm_op, I, d, s64, 64) \이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ATOMIC_OP_RETURN(op, asm_op, c_op, I, d, s64, 64)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
ATOMIC_OPS(add, add, +, i)32/64-bit add의 relaxed와 ordered variant를 macro로 생성한다.
ATOMIC_OPS(sub, add, +, -i)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
(blank)빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define arch_atomic_add_return_relaxed arch_atomic_add_return_relaxedcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic_sub_return_relaxed arch_atomic_sub_return_relaxedcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic_add_return arch_atomic_add_returncompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic_sub_return arch_atomic_sub_returncompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define arch_atomic_fetch_add_relaxed arch_atomic_fetch_add_relaxedcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic_fetch_sub_relaxed arch_atomic_fetch_sub_relaxedcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic_fetch_add arch_atomic_fetch_addcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic_fetch_sub arch_atomic_fetch_subcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifndef CONFIG_GENERIC_ATOMIC64Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define arch_atomic64_add_return_relaxed arch_atomic64_add_return_relaxedcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic64_sub_return_relaxed arch_atomic64_sub_return_relaxedcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic64_add_return arch_atomic64_add_returncompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic64_sub_return arch_atomic64_sub_returncompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define arch_atomic64_fetch_add_relaxed arch_atomic64_fetch_add_relaxedcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic64_fetch_sub_relaxed arch_atomic64_fetch_sub_relaxedcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic64_fetch_add arch_atomic64_fetch_addcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define arch_atomic64_fetch_sub arch_atomic64_fetch_subcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#undef ATOMIC_OPS이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_GENERIC_ATOMIC64Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define ATOMIC_OPS(op, asm_op, I) \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
ATOMIC_FETCH_OP(op, asm_op, I, w, int, )이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
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 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.