← Architecture 비교

Linux 6.18.37 LTS · Architecture comparison 07/21

Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO

atomic API의 relaxed/acquire/release/full variant가 세 ISA에서 어떤 instruction과 retry loop로 내려가는지 읽습니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
파일 3개 · 원문 181줄
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

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 양쪽에서 이동하지 않게 해야 한다.

지연 시간 관점uncontended RMW, cache-line transfer, LL/SC retry, qspinlock slow path와 virtualization paravirt hook을 분리한다. worst case는 lock holder preemption과 NUMA line bouncing이 결정한다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64LSE atomic 또는 LL/SC alternativeexclusive reservation이 잦게 깨지거나 잘못된 relaxed variant를 사용해 protected data가 늦게 보인다.LSE capability, selected alternative, retry count, cache line owner와 acquire/release suffix를 본다.
x86-64LOCK prefix RMW와 CMPXCHG/XCHGfalse sharing으로 unrelated atomic이 같은 line을 왕복하거나 PV hook 불일치로 lockup이 난다.LOCK instruction, cache line, qspinlock val, pending/tail, holder CPU와 PV mode를 확인한다.
RISC-VAMO aq/rl 또는 LR/SC와 Zacas capabilityreservation granule contention이나 misaligned atomic이 progress/fault 문제를 만든다.AMO width, aq/rl, LR/SC retry, reservation granule와 ISA extension을 확인한다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

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을 확인한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contract원자적 read-modify-write와 memory order는 별개 축이다. relaxed는 tear/lost-update를 막지만 주변 일반 load/store의 order를 전부 보장하지 않는다. lock slow path는 contention queue와 fairness를 추가한다.
arm64LSE atomic 또는 LL/SC alternativeLL/SC loop 실패와 barrier variant가 return value publication 순서를 결정한다.
x86-64LOCK prefix RMW와 CMPXCHG/XCHGTSO보다 강한 locked op를 단순 load/store와 구분하고 compiler memory clobber를 확인한다.
RISC-VAMO aq/rl 또는 LR/SC와 Zacas capabilityRVWMO에서는 aq/rl가 없는 relaxed AMO 주변의 일반 memory order를 별도 fence가 보완해야 한다.
lifetime boundarylock word와 보호 객체는 모든 waiter가 queue에서 빠질 때까지 살아 있어야 한다. lock을 포함한 object를 unlock 직후 free하면 다음 waiter가 재사용 memory를 lock으로 해석할 수 있다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비LL/SC loop 실패와 barrier variant가 return value publication 순서를 결정한다.관찰: LSE capability, selected alternative, retry count, cache line owner와 acquire/release suffix를 본다.
x86-64state 준비TSO보다 강한 locked op를 단순 load/store와 구분하고 compiler memory clobber를 확인한다.관찰: LOCK instruction, cache line, qspinlock val, pending/tail, holder CPU와 PV mode를 확인한다.
RISC-Vstate 준비RVWMO에서는 aq/rl가 없는 relaxed AMO 주변의 일반 memory order를 별도 fence가 보완해야 한다.관찰: 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개 구문에 설명을 붙였습니다.

L30ATOMIC_OP(atomic_sub)

atomic_t에서 i를 원자적으로 빼고 반환값은 주지 않는 arch_atomic_sub 함수를 생성합니다. 실제 명령 경로는 LSE 지원에 따라 선택됩니다.

L32#undef ATOMIC_OP

반환값 없는 32비트 함수 생성에 사용한 임시 ATOMIC_OP 매크로를 해제합니다. 이미 생성된 함수 정의는 남습니다.

L34#define ATOMIC_FETCH_OP(name, op) \

반환값이 있는 32비트 원자 연산 함수를 만드는 틀입니다. op와 name을 이어 함수명과 순서 보장 종류를 정하고, __lse_ll_sc_body가 CPU에 맞는 LSE 또는 LL/SC 구현으로 넘깁니다. fetch 계열은 변경 전 값을, add_return·sub_return 계열은 변경 후 값을 돌려줍니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L35static __always_inline int arch_##op##name(int i, atomic_t *v) \

연산명 op와 순서 접미사 name을 붙인 32비트 atomic 함수를 정의합니다. i는 연산 값, v는 대상이며 int 반환값의 의미는 fetch 또는 return 연산 종류에 따라 달라집니다.

L37 return __lse_ll_sc_body(op##name, i, v); \

boot-time alternative가 LSE 또는 LL/SC 구현을 선택하는 공통 호출점이다.

L40#define ATOMIC_FETCH_OPS(op) \

같은 32비트 연산에 relaxed·acquire·release·기본형 네 함수를 만듭니다. relaxed는 다른 메모리 접근의 순서를 추가로 보장하지 않고, acquire와 release는 각각 뒤와 앞의 접근을 연결하며, 기본형은 완전한 순서 보장을 제공하는 구현을 선택합니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L41 ATOMIC_FETCH_OP(_relaxed, op) \

relaxed 변형을 생성합니다. 대상 값의 원자성은 보장하지만 앞뒤 일반 메모리 접근에 acquire/release 순서를 추가하지 않습니다.

L42 ATOMIC_FETCH_OP(_acquire, op) \

acquire 변형을 생성합니다. 이 원자적 읽기 뒤의 메모리 접근이 읽기보다 앞서 관찰되지 않게 하여 공개된 자료를 읽는 쪽에 사용합니다.

L43 ATOMIC_FETCH_OP(_release, op) \

release 변형을 생성합니다. 앞선 메모리 접근이 원자 갱신 뒤로 넘어가지 않게 하여 갱신과 함께 자료를 공개할 때 사용합니다.

L44 ATOMIC_FETCH_OP( , op)

접미사 없는 기본 변형을 생성합니다. Linux의 이 반환값 있는 atomic 연산은 완전한 메모리 순서 보장을 제공하는 기본 API입니다.

L46ATOMIC_FETCH_OPS(atomic_fetch_andnot)

32비트 값에 i에서 1인 비트를 지우는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.

L47ATOMIC_FETCH_OPS(atomic_fetch_or)

32비트 값에 i에서 1인 비트를 켜는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.

L48ATOMIC_FETCH_OPS(atomic_fetch_xor)

32비트 값에 i에서 1인 비트를 반전하는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.

L49ATOMIC_FETCH_OPS(atomic_fetch_add)

32비트 값에 i를 더하는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.

L50ATOMIC_FETCH_OPS(atomic_fetch_and)

32비트 값에 i에서 0인 비트를 지우는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.

L51ATOMIC_FETCH_OPS(atomic_fetch_sub)

32비트 값에 i를 빼는 fetch 연산의 네 가지 순서 변형을 생성합니다. 모두 원자 갱신 전 값을 반환하므로 다른 CPU와 경쟁하면서 이전 상태를 얻을 수 있습니다.

L52ATOMIC_FETCH_OPS(atomic_add_return)

relaxed/acquire/release/full 네 ordering variant를 생성한다.

L53ATOMIC_FETCH_OPS(atomic_sub_return)

i를 뺀 뒤의 새 값을 반환하는 atomic_sub_return 계열 네 가지를 생성합니다. fetch_sub가 갱신 전 값을 주는 것과 반환 기준이 다릅니다.

L55#undef ATOMIC_FETCH_OP

단일 반환형 atomic 함수를 만들던 임시 ATOMIC_FETCH_OP 매크로를 해제합니다. 다음 64비트 생성 매크로와 이름·범위를 분리합니다.

L56#undef ATOMIC_FETCH_OPS

네 가지 순서 변형을 묶어 만들던 임시 매크로를 해제합니다. 생성된 32비트 함수들은 그대로 사용할 수 있습니다.

L58#define ATOMIC64_OP(op) \

반환값이 없는 64비트 원자 연산 함수를 만듭니다. long 값 i와 atomic64_t를 받아 op에 맞는 LSE 또는 LL/SC 구현에서 카운터를 갱신하며, 갱신 전후 값을 호출자에게 돌려주지는 않습니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L59static __always_inline void arch_##op(long i, atomic64_t *v) \

반환값 없는 64비트 atomic 함수를 정의하는 매크로 본문입니다. long i를 atomic64_t 대상 v에 적용하며 op가 실제 연산 이름을 정합니다.

L61 __lse_ll_sc_body(op, i, v); \

LSE 원자 명령과 LL/SC 반복 중 CPU 기능에 맞는 op 구현을 선택하여 i와 v를 전달합니다. 이 64비트 함수는 결과값을 호출자에게 반환하지 않습니다.

L64ATOMIC64_OP(atomic64_andnot)

64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i에서 1인 비트를 지웁니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.

L65ATOMIC64_OP(atomic64_or)

64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i에서 1인 비트를 켭니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.

L66ATOMIC64_OP(atomic64_xor)

64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i에서 1인 비트를 반전합니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.

L67ATOMIC64_OP(atomic64_add)

64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i를 더합니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.

L68ATOMIC64_OP(atomic64_and)

64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i와 비트별 AND를 합니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.

L69ATOMIC64_OP(atomic64_sub)

64비트 atomic 함수를 생성하여 대상 값에 원자적으로 연산합니다. 생성 함수는 i를 뺍니다. 반환값은 없으며 실제 LSE 또는 LL/SC 구현은 공통 선택 매크로에 맡깁니다.

L71#undef ATOMIC64_OP

64비트 무반환 함수 생성이 끝나 임시 ATOMIC64_OP 매크로를 없앱니다. 컴파일된 atomic 함수가 삭제되는 것은 아닙니다.

L73#define ATOMIC64_FETCH_OP(name, op) \

반환값이 있는 64비트 원자 연산 함수를 만듭니다. op와 순서 접미사 name을 합쳐 실제 연산을 선택하며 fetch는 변경 전 값, add_return·sub_return은 변경 후 값을 long으로 돌려줍니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L74static __always_inline long arch_##op##name(long i, atomic64_t *v) \

64비트 반환형 atomic 함수의 이름과 인자를 정의합니다. op와 name을 붙인 함수가 long 결과를 반환하며 i는 피연산자, v는 갱신 대상입니다.

L76 return __lse_ll_sc_body(op##name, i, v); \

boot-time alternative가 LSE 또는 LL/SC 구현을 선택하는 공통 호출점이다.

L79#define ATOMIC64_FETCH_OPS(op) \

64비트 연산마다 relaxed·acquire·release·기본형을 함께 정의합니다. 데이터 폭은 모두 64비트이고, 다른 메모리 접근과 어떤 순서를 보장할지만 달라지므로 각 호출자가 필요한 순서 수준을 고를 수 있습니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L80 ATOMIC64_FETCH_OP(_relaxed, op) \

64비트 연산의 relaxed 변형을 생성합니다. 대상 값의 원자적 갱신만 보장하고 주변 접근의 acquire/release 순서는 추가하지 않습니다.

L81 ATOMIC64_FETCH_OP(_acquire, op) \

64비트 연산의 acquire 변형을 생성합니다. 원자적 읽기 이후의 접근을 그 읽기보다 앞서지 않게 정렬합니다.

L82 ATOMIC64_FETCH_OP(_release, op) \

64비트 연산의 release 변형을 생성합니다. 앞서 준비한 데이터 접근이 원자적 갱신 이후로 넘어가지 않게 합니다.

L83 ATOMIC64_FETCH_OP( , op)

64비트 연산의 접미사 없는 기본 변형을 생성합니다. 이 반환형 atomic API의 완전한 메모리 순서 보장을 선택합니다.

L85ATOMIC64_FETCH_OPS(atomic64_fetch_andnot)

64비트 대상에 i에서 1인 비트를 지우는 fetch 함수들을 생성합니다. 갱신 전 값을 반환하며 relaxed·acquire·release·기본 순서의 네 API를 제공합니다.

L86ATOMIC64_FETCH_OPS(atomic64_fetch_or)

64비트 대상에 i에서 1인 비트를 켜는 fetch 함수들을 생성합니다. 갱신 전 값을 반환하며 relaxed·acquire·release·기본 순서의 네 API를 제공합니다.

L87ATOMIC64_FETCH_OPS(atomic64_fetch_xor)

64비트 대상에 i에서 1인 비트를 반전하는 fetch 함수들을 생성합니다. 갱신 전 값을 반환하며 relaxed·acquire·release·기본 순서의 네 API를 제공합니다.

L88ATOMIC64_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개 구문에 설명을 붙였습니다.

L75#define arch_atomic_inc_and_test arch_atomic_inc_and_test

arch_atomic_inc_and_test이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 32비트 카운터를 1 늘린 결과가 0인지 반환합니다.

L77static __always_inline bool arch_atomic_add_negative(int i, atomic_t *v)

v에 i를 원자적으로 더하고 결과가 음수인지 bool로 반환하는 함수입니다. 카운터 값 자체 대신 덧셈 결과의 부호를 필요로 할 때 사용합니다.

L79 return GEN_BINARY_RMWcc(LOCK_PREFIX "addl", v->counter, s, "er", i);

LOCK이 붙은 32비트 덧셈으로 counter를 갱신하고 조건 코드 s로 부호 플래그를 결과에 담습니다. 반환값은 이전 값이나 합계가 아니라 합계가 음수인지 여부입니다.

L81#define arch_atomic_add_negative arch_atomic_add_negative

arch_atomic_add_negative이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 32비트 카운터에 i를 더한 결과가 음수인지 반환합니다.

L83static __always_inline int arch_atomic_add_return(int i, atomic_t *v)

i를 v에 원자적으로 더한 뒤 새 합계를 반환하는 함수입니다. 내부 xadd가 주는 이전 값에 i를 더해 반환값을 만듭니다.

L85 return i + xadd(&v->counter, i);

XADD가 old value를 반환하므로 operand i와 합쳐 new value를 만든다.

L87#define arch_atomic_add_return arch_atomic_add_return

arch_atomic_add_return이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 32비트 카운터에 i를 더한 뒤의 새 값을 반환합니다.

L89#define arch_atomic_sub_return(i, v) arch_atomic_add_return(-(i), v)

i를 빼는 연산을 -i를 더하는 arch_atomic_add_return으로 구현합니다. 같은 원자적 덧셈 경로를 재사용하되 반환값은 감산이 끝난 뒤의 카운터 값입니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L91static __always_inline int arch_atomic_fetch_add(int i, atomic_t *v)

i를 원자적으로 더하되 갱신 전 값을 반환하는 fetch_add 함수입니다. 여러 CPU가 같은 카운터에서 서로 다른 이전 값을 확보할 때 사용합니다.

L93 return xadd(&v->counter, i);

xadd로 counter에 i를 더하고 덧셈 전 값을 반환합니다. 단순 load와 add로 나누지 않아 경쟁 중 갱신을 잃지 않습니다.

L95#define arch_atomic_fetch_add arch_atomic_fetch_add

arch_atomic_fetch_add이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 32비트 카운터에 i를 더하면서 변경 전 값을 반환합니다.

L97#define arch_atomic_fetch_sub(i, v) arch_atomic_fetch_add(-(i), v)

i를 빼는 연산을 -i를 더하는 arch_atomic_fetch_add로 바꿉니다. fetch 계열이므로 반환값은 감산 결과가 아니라 갱신 전 카운터 값입니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L99static __always_inline int arch_atomic_cmpxchg(atomic_t *v, int old, int new)

v가 old와 같을 때만 new로 바꾸고 실제 이전 값을 반환하는 비교 교환 함수입니다. 반환값과 old를 비교하면 교환 성공 여부를 알 수 있습니다.

L101 return arch_cmpxchg(&v->counter, old, new);

expected value가 일치할 때만 한 cache-line transaction으로 교체한다.

L103#define arch_atomic_cmpxchg arch_atomic_cmpxchg

arch_atomic_cmpxchg이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 카운터가 예상값 old와 같을 때만 new로 바꾸고 관찰한 이전 값을 반환합니다.

L105static __always_inline bool arch_atomic_try_cmpxchg(atomic_t *v, int *old, int new)

v가 *old와 같으면 new로 바꾸고 성공 여부를 bool로 반환합니다. 실패하면 관측한 실제 값을 *old에 써 다음 시도의 비교값으로 사용할 수 있게 합니다.

L107 return arch_try_cmpxchg(&v->counter, old, new);

counter 주소와 old 포인터, new 값을 x86 비교 교환 구현에 전달합니다. 성공 여부를 반환하고 실패 시 기대값 포인터를 실제 값으로 갱신하는 API를 그대로 제공합니다.

L109#define arch_atomic_try_cmpxchg arch_atomic_try_cmpxchg

arch_atomic_try_cmpxchg이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 예상값과 일치할 때 new로 바꾸어 성공 여부를 반환하고 실패하면 *old에 관찰값을 기록합니다.

L111static __always_inline int arch_atomic_xchg(atomic_t *v, int new)

counter를 무조건 new로 원자 교환하고 교환 전 값을 반환하는 함수입니다. 기대값을 비교하는 cmpxchg와 달리 일치 조건이 없습니다.

L113 return arch_xchg(&v->counter, new);

counter에 대한 x86 원자 교환을 실행하고 이전 값을 반환합니다. 메모리 피연산자의 XCHG를 사용하여 다른 CPU와의 교환도 원자적으로 처리합니다.

L115#define arch_atomic_xchg arch_atomic_xchg

arch_atomic_xchg이라는 x86 구현이 있음을 공통 atomic 계층의 전처리 검사에 알립니다. 자기 이름으로 치환되므로 재귀 호출은 생기지 않습니다. 실제 동명 함수는 카운터를 new로 원자적으로 바꾸고 이전 값을 반환합니다.

L117static __always_inline void arch_atomic_and(int i, atomic_t *v)

v->counter와 i의 비트별 AND를 원자적으로 수행하는 함수입니다. i에서 0인 비트를 지우며 별도 결과값은 반환하지 않습니다.

L119 asm_inline volatile(LOCK_PREFIX "andl %1, %0"

32비트 메모리 AND 명령에 LOCK 접두사를 적용하여 원자 갱신을 수행합니다. volatile은 컴파일러가 이 어셈블리를 불필요하다고 없애지 못하게 합니다.

L120 : "+m" (v->counter)

+m은 counter 메모리를 입력으로 읽고 출력으로 다시 쓴다는 제약입니다. 컴파일러에 이 명령의 실제 갱신 대상을 알립니다.

L121 : "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개 구문에 설명을 붙였습니다.

L92 register c_type ret; \

원자적 연산 전의 값을 받을 ret 변수를 선언합니다. c_type은 생성할 atomic 함수의 정수형이며, register는 컴파일러에 주는 저장 방식 힌트입니다. 반환 명령이나 권한 전환을 실행하는 줄이 아닙니다.

L93 __asm__ __volatile__ ( \

갱신 전 값을 돌려주는 relaxed AMO 명령의 인라인 어셈블리 구간을 시작합니다. volatile은 이 원자 연산을 임의로 제거하지 못하게 합니다.

L94 " amo" #asm_op "." #asm_type " %1, %2, %0" \

AMO instruction이 memory word와 register 결과를 한 원자적 transaction으로 갱신한다.

L95 : "+A" (v->counter), "=r" (ret) \

인라인 어셈블리의 출력 조건을 지정합니다. +A는 v->counter 메모리를 읽고 갱신한다는 뜻이고, =r은 AMO가 읽어 온 갱신 전 값을 레지스터 출력 ret에 받겠다는 뜻입니다.

L96 : "r" (I) \

AMO에 더하거나 비트 연산할 값 I를 일반 레지스터 입력으로 전달합니다. 뺄셈 변형에서는 I가 -i로 확장되어 덧셈 명령으로 뺄셈을 구현합니다.

L97 : "memory"); \

memory clobber로 컴파일러에 메모리 영향이 있음을 알리고 어셈블리를 마칩니다. 이 표기만으로 하드웨어 acquire/release 순서가 추가되는 것은 아닙니다.

L98 return ret; \

AMO로 갱신하기 전의 값을 호출자에게 돌려줍니다. fetch 계열 atomic 함수의 반환 규칙이며, 이 relaxed 버전은 연산 앞뒤의 일반 메모리 접근에 acquire/release 순서를 추가하지 않습니다.

L100static __always_inline \

기본 순서 fetch 함수에 내부 연결과 강제 인라인 속성을 붙입니다. 실제 타입·함수명·인자는 다음 줄의 매크로 조합으로 완성됩니다.

L101c_type arch_atomic##prefix##_fetch_##op(c_type i, atomic##prefix##_t *v) \

prefix와 op를 붙여 32비트 또는 64비트 fetch 함수 이름을 만듭니다. i를 대상 v에 적용하고 갱신 전 값을 c_type으로 반환합니다.

L103 register c_type ret; \

순서 보장이 있는 fetch 연산에서 갱신 전 값을 받을 ret 변수를 선언합니다. c_type은 매크로 인자로 정하는 자료형이며, 이 선언만으로 레지스터나 실행 문맥을 복원하지 않습니다.

L104 __asm__ __volatile__ ( \

완전한 순서 보장을 가진 fetch AMO의 어셈블리를 시작합니다. 바로 뒤 명령의 .aqrl이 acquire와 release를 함께 부여합니다.

L105 " amo" #asm_op "." #asm_type ".aqrl %1, %2, %0" \

AMO instruction이 memory word와 register 결과를 한 원자적 transaction으로 갱신한다.

L106 : "+A" (v->counter), "=r" (ret) \

v->counter를 읽고 쓰는 메모리 피연산자로, ret를 레지스터 출력으로 지정합니다. 앞의 .aqrl AMO 명령이 원자 갱신과 메모리 순서 보장을 수행하고 ret에는 갱신 전 값을 남깁니다.

L107 : "r" (I) \

순서 보장형 AMO의 연산 값 I를 레지스터로 전달합니다. 주소 입력과 결과 출력은 앞줄에, 실제 산술·비트 연산 피연산자는 여기에 명시합니다.

L108 : "memory"); \

어셈블리가 메모리에 영향을 준다는 compiler clobber를 선언합니다. 하드웨어 순서는 앞의 .aqrl이 맡고 컴파일러의 재배치도 이 경계에서 제한합니다.

L109 return ret; \

원자 연산으로 바꾸기 전의 counter 값을 반환합니다. acquire/release 순서 보장은 앞의 .aqrl 명령에서 이루어졌으며, 이 줄은 그 결과를 C 호출자에게 전달합니다.

L112#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 명령 실행은 아닙니다.

L113static __always_inline \

갱신 후 값을 반환하는 relaxed 함수의 내부 연결·인라인 속성을 지정합니다. 아래의 fetch 연산 결과에서 새 값을 계산하는 작은 함수를 호출 지점에 펼칩니다.

L114c_type arch_atomic##prefix##_##op##_return_relaxed(c_type i, \

op_return_relaxed 이름의 반환형 원자 함수를 정의하기 시작합니다. i가 갱신량이며 반환값은 연산 전 값이 아니라 연산 후 값입니다.

L115 atomic##prefix##_t *v) \

원자 변수 포인터 v를 두 번째 인자로 받으며 함수 정의를 마칩니다. prefix에 따라 atomic_t 또는 atomic64_t가 선택됩니다.

L117 return arch_atomic##prefix##_fetch_##op##_relaxed(i, v) c_op I; \

relaxed fetch 연산이 돌려준 이전 값에 c_op와 I를 적용해 갱신 후 값을 반환합니다. 메모리는 fetch에서 한 번만 원자 갱신하며 뒤의 계산은 반환값을 만드는 계산입니다.

L119static __always_inline \

기본 순서의 갱신 후 값 반환 함수에 내부 연결과 인라인 속성을 부여합니다. 함수 호출 비용 없이 뒤의 원자 연산을 사용할 수 있게 합니다.

L120c_type arch_atomic##prefix##_##op##_return(c_type i, atomic##prefix##_t *v) \

op_return 함수의 이름·정수형·대상 포인터를 생성합니다. relaxed 접미사가 없으므로 아래의 기본 순서 fetch 연산을 사용합니다.

L122 return arch_atomic##prefix##_fetch_##op(i, v) c_op I; \

순서 보장형 fetch가 준 이전 값으로 새 값을 계산해 반환합니다. 실제 원자 갱신과 .aqrl 순서 보장은 호출한 fetch 함수에 들어 있습니다.

L125#ifdef CONFIG_GENERIC_ATOMIC64

GENERIC_ATOMIC64를 사용하는 빌드는 64비트 원자 연산을 공통 구현에 맡깁니다. 이 분기는 여기서 32비트 AMO 함수만 생성하며, 공통 구현을 쓰지 않는 분기에서는 64비트 AMO 함수도 함께 생성합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.

L126#define ATOMIC_OPS(op, asm_op, c_op, I) \

CONFIG_GENERIC_ATOMIC64 구성에서는 이 틀이 32비트 AMO의 fetch형과 변경 후 값 반환형만 만듭니다. 64비트 원자 연산은 별도의 공통 구현이 담당하므로 여기서 64비트 AMO 함수를 중복 생성하지 않습니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L127 ATOMIC_FETCH_OP( op, asm_op, I, w, int, ) \

32비트 AMO.W와 int를 사용하는 fetch 함수들을 생성합니다. GENERIC_ATOMIC64 구성에서는 64비트 원자 연산을 공통 구현에 맡겨 여기서 생성하지 않습니다.

L128 ATOMIC_OP_RETURN(op, asm_op, c_op, I, w, int, )

32비트 fetch 결과에서 갱신 후 값을 계산하는 return 함수들을 생성합니다. 메모리 대상은 atomic_t이며 64비트형은 공통 atomic64 구현이 담당합니다.

L129#else

공통 atomic64 구현을 쓰지 않는 빌드에서는 AMO word 연산의 32비트 함수와 AMO doubleword 연산의 64비트 함수를 모두 생성합니다. 두 정수 폭에 맞는 구현을 아키텍처가 직접 제공합니다.

L130#define ATOMIC_OPS(op, asm_op, c_op, I) \

64비트 원자 연산을 공통 대체 구현에 맡기지 않는 구성의 틀입니다. w/int 조합으로 32비트, d/s64/64 조합으로 64비트 AMO 함수를 만들며 각 폭에 fetch형과 변경 후 값 반환형을 모두 제공합니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L131 ATOMIC_FETCH_OP( op, asm_op, I, w, int, ) \

하드웨어 64비트 atomic을 사용하는 구성에서도 기본 32비트 fetch 함수를 함께 생성합니다. w와 int가 명령 폭과 C 자료형을 정합니다.

L132 ATOMIC_OP_RETURN(op, asm_op, c_op, I, w, int, ) \

32비트 atomic의 갱신 후 값 반환 함수를 생성합니다. 이어지는 64비트 생성과 구분하여 prefix 없이 정의합니다.

L133 ATOMIC_FETCH_OP( op, asm_op, I, d, s64, 64) \

AMO.D와 s64를 사용해 64비트 fetch 함수들을 생성합니다. prefix 64가 함수명과 atomic64_t 형에 반영됩니다.

L134 ATOMIC_OP_RETURN(op, asm_op, c_op, I, d, s64, 64)

64비트 fetch가 돌려준 이전 값에서 새 값을 계산하는 return 함수들을 생성합니다. 64비트 정수 연산으로 결과 폭을 유지합니다.

L135#endif

125행에서 시작한 선택 구간을 마칩니다. GENERIC_ATOMIC64를 사용하는 빌드는 64비트 원자 연산을 공통 구현에 맡깁니다. 이 분기는 여기서 32비트 AMO 함수만 생성하며, 공통 구현을 쓰지 않는 분기에서는 64비트 AMO 함수도 함께 생성합니다.

L137ATOMIC_OPS(add, add, +, i)

32/64-bit add의 relaxed와 ordered variant를 macro로 생성한다.

L138ATOMIC_OPS(sub, add, +, -i)

sub API를 AMO ADD와 -i 조합으로 생성합니다. 이전 값에 -i를 더하는 방식으로 원자 뺄셈을 구현하고, return 변형도 같은 계산으로 새 값을 만듭니다.

L140#define arch_atomic_add_return_relaxed arch_atomic_add_return_relaxed

위의 생성 매크로가 만든 arch_atomic_add_return_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 더하고 변경 후 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.

L141#define arch_atomic_sub_return_relaxed arch_atomic_sub_return_relaxed

위의 생성 매크로가 만든 arch_atomic_sub_return_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 빼고 변경 후 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.

L142#define arch_atomic_add_return arch_atomic_add_return

위의 생성 매크로가 만든 arch_atomic_add_return을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 더하고 변경 후 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.

L143#define arch_atomic_sub_return arch_atomic_sub_return

위의 생성 매크로가 만든 arch_atomic_sub_return을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 빼고 변경 후 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.

L145#define arch_atomic_fetch_add_relaxed arch_atomic_fetch_add_relaxed

위의 생성 매크로가 만든 arch_atomic_fetch_add_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 더하고 변경 전 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.

L146#define arch_atomic_fetch_sub_relaxed arch_atomic_fetch_sub_relaxed

위의 생성 매크로가 만든 arch_atomic_fetch_sub_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 빼고 변경 전 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.

L147#define arch_atomic_fetch_add arch_atomic_fetch_add

위의 생성 매크로가 만든 arch_atomic_fetch_add을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 더하고 변경 전 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.

L148#define arch_atomic_fetch_sub arch_atomic_fetch_sub

위의 생성 매크로가 만든 arch_atomic_fetch_sub을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 32비트 카운터에 i를 빼고 변경 전 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.

L150#ifndef CONFIG_GENERIC_ATOMIC64

64비트 원자 연산을 공통 구현에 맡기지 않는 경우에만 아키텍처의 atomic64 함수 이름들을 공개합니다. 공통 구현과 같은 이름의 64비트 연산이 중복 정의되지 않게 하는 선택입니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.

L151#define arch_atomic64_add_return_relaxed arch_atomic64_add_return_relaxed

위의 생성 매크로가 만든 arch_atomic64_add_return_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 더하고 변경 후 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.

L152#define arch_atomic64_sub_return_relaxed arch_atomic64_sub_return_relaxed

위의 생성 매크로가 만든 arch_atomic64_sub_return_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 빼고 변경 후 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.

L153#define arch_atomic64_add_return arch_atomic64_add_return

위의 생성 매크로가 만든 arch_atomic64_add_return을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 더하고 변경 후 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.

L154#define arch_atomic64_sub_return arch_atomic64_sub_return

위의 생성 매크로가 만든 arch_atomic64_sub_return을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 빼고 변경 후 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.

L156#define arch_atomic64_fetch_add_relaxed arch_atomic64_fetch_add_relaxed

위의 생성 매크로가 만든 arch_atomic64_fetch_add_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 더하고 변경 전 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.

L157#define arch_atomic64_fetch_sub_relaxed arch_atomic64_fetch_sub_relaxed

위의 생성 매크로가 만든 arch_atomic64_fetch_sub_relaxed을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 빼고 변경 전 값을 반환합니다. relaxed형은 원자적 갱신만 요구하며 다른 메모리 접근과의 순서를 추가로 보장하지 않습니다.

L158#define arch_atomic64_fetch_add arch_atomic64_fetch_add

위의 생성 매크로가 만든 arch_atomic64_fetch_add을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 더하고 변경 전 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.

L159#define arch_atomic64_fetch_sub arch_atomic64_fetch_sub

위의 생성 매크로가 만든 arch_atomic64_fetch_sub을 공통 atomic 계층에서 사용할 수 있다고 표시합니다. 같은 이름으로 치환되는 정의라 실행 코드를 추가하지 않습니다. 실제 함수는 64비트 카운터에 i를 빼고 변경 전 값을 반환합니다. 접미사가 없는 이 구현은 .aqrl AMO를 사용해 앞뒤 메모리 접근의 순서도 보장합니다.

L160#endif

150행에서 시작한 선택 구간을 마칩니다. 64비트 원자 연산을 공통 구현에 맡기지 않는 경우에만 아키텍처의 atomic64 함수 이름들을 공개합니다. 공통 구현과 같은 이름의 64비트 연산이 중복 정의되지 않게 하는 선택입니다.

L162#undef ATOMIC_OPS

덧셈·뺄셈용 ATOMIC_OPS 매크로를 해제합니다. 아래에서 비트 연산에 필요한 다른 인자 목록으로 같은 이름을 다시 정의합니다.

L164#ifdef CONFIG_GENERIC_ATOMIC64

64비트 원자 연산에 공통 구현을 쓰는 경우 아래 연산 생성 매크로는 32비트 함수만 만듭니다. 반대 분기는 doubleword AMO를 사용하는 64비트 함수까지 생성합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.

L165#define ATOMIC_OPS(op, asm_op, I) \

이 지점에서는 논리 연산용으로 ATOMIC_OPS를 다시 정의합니다. CONFIG_GENERIC_ATOMIC64 구성이므로 and·or·xor의 32비트 fetch형만 만들고, 64비트 연산은 공통 구현에 맡깁니다. 앞의 산술용 틀과 달리 변경 후 값을 계산하는 OP_RETURN은 만들지 않습니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L166 ATOMIC_FETCH_OP(op, asm_op, I, w, int, )

32비트 비트 연산용 fetch 함수들을 생성합니다. 비트 연산에는 갱신 후 값을 주는 별도 return 계열을 만들지 않으며 64비트는 GENERIC_ATOMIC64 경로에 맡깁니다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

contended atomic의 cache-line 이동 비용

4 CPU가 서로 다른 socket/hart cluster에서 같은 counter를 1,000,000회 증가시키고 line ownership 이동이 평균 120ns라고 가정한다.

  1. serialization한 cache line의 RMW는 사실상 ownership을 직렬화한다.
  2. lower bound1,000,000 x 120ns = 120ms가 coherence 이동만의 하한이다.
  3. LL/SCretry가 평균 1.4회면 load/store-exclusive 시도 수는 1.4M으로 늘어난다.
  4. alternativeper-CPU counter 후 합산하면 hot path line bouncing을 제거하고 read 시 aggregation 비용을 낸다.

결론atomic instruction 하나의 cycle이 아니라 contention과 cache topology를 포함해 upper bound를 잡는다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 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 양쪽에서 이동하지 않게 해야 한다.

02

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를 본다.

03

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를 확인한다.

04

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을 확인한다.

05

객체 수명과 소유권을 먼저 고정한다

lock word와 보호 객체는 모든 waiter가 queue에서 빠질 때까지 살아 있어야 한다. lock을 포함한 object를 unlock 직후 free하면 다음 waiter가 재사용 memory를 lock으로 해석할 수 있다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

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

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64exclusive reservation이 잦게 깨지거나 잘못된 relaxed variant를 사용해 protected data가 늦게 보인다.LSE capability, selected alternative, retry count, cache line owner와 acquire/release suffix를 본다.
x86-64false sharing으로 unrelated atomic이 같은 line을 왕복하거나 PV hook 불일치로 lockup이 난다.LOCK instruction, cache line, qspinlock val, pending/tail, holder CPU와 PV mode를 확인한다.
RISC-Vreservation granule contention이나 misaligned atomic이 progress/fault 문제를 만든다.AMO width, aq/rl, LR/SC retry, reservation granule와 ISA extension을 확인한다.

08 · LAB

재현과 계측 절차

  1. 같은 atomic을 relaxed와 full variant로 빌드해 disassembly와 litmus outcome을 비교한다.
  2. lockstat/perf c2c로 qspinlock slow path와 false sharing cache line을 찾는다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.