← Architecture 비교DUJINLABS.COM

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 files · 181 annotated lines
기준 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에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 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개 줄에 각각 설명을 붙였습니다.

L30ATOMIC_OP(atomic_sub)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L31(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L32#undef ATOMIC_OP

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L33(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L34#define ATOMIC_FETCH_OP(name, op) \

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L35static __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를 분리해 해석한다.

L36{ \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

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

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

L38}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L39(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L40#define ATOMIC_FETCH_OPS(op) \

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L41 ATOMIC_FETCH_OP(_relaxed, op) \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L42 ATOMIC_FETCH_OP(_acquire, op) \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L43 ATOMIC_FETCH_OP(_release, op) \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L44 ATOMIC_FETCH_OP( , op)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L45(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L46ATOMIC_FETCH_OPS(atomic_fetch_andnot)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L47ATOMIC_FETCH_OPS(atomic_fetch_or)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L48ATOMIC_FETCH_OPS(atomic_fetch_xor)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L49ATOMIC_FETCH_OPS(atomic_fetch_add)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L50ATOMIC_FETCH_OPS(atomic_fetch_and)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L51ATOMIC_FETCH_OPS(atomic_fetch_sub)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L52ATOMIC_FETCH_OPS(atomic_add_return)

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

L53ATOMIC_FETCH_OPS(atomic_sub_return)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L54(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L55#undef ATOMIC_FETCH_OP

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L56#undef ATOMIC_FETCH_OPS

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L57(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L58#define ATOMIC64_OP(op) \

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L59static __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를 분리해 해석한다.

L60{ \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L61 __lse_ll_sc_body(op, i, v); \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L62}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L63(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L64ATOMIC64_OP(atomic64_andnot)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L65ATOMIC64_OP(atomic64_or)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L66ATOMIC64_OP(atomic64_xor)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L67ATOMIC64_OP(atomic64_add)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L68ATOMIC64_OP(atomic64_and)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L69ATOMIC64_OP(atomic64_sub)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L70(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L71#undef ATOMIC64_OP

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L72(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L73#define ATOMIC64_FETCH_OP(name, op) \

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L74static __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를 분리해 해석한다.

L75{ \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

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

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

L77}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L78(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L79#define ATOMIC64_FETCH_OPS(op) \

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L80 ATOMIC64_FETCH_OP(_relaxed, op) \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L81 ATOMIC64_FETCH_OP(_acquire, op) \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L82 ATOMIC64_FETCH_OP(_release, op) \

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L83 ATOMIC64_FETCH_OP( , op)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L84(blank)

빈 줄은 arm64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L85ATOMIC64_FETCH_OPS(atomic64_fetch_andnot)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L86ATOMIC64_FETCH_OPS(atomic64_fetch_or)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L87ATOMIC64_FETCH_OPS(atomic64_fetch_xor)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

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

L75#define arch_atomic_inc_and_test arch_atomic_inc_and_test

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L76(blank)

빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

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

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L78{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L79 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가 정리됐는지 확인한다.

L80}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L81#define arch_atomic_add_negative arch_atomic_add_negative

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L82(blank)

빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

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

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L84{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

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

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

L86}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L87#define arch_atomic_add_return arch_atomic_add_return

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L88(blank)

빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

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

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L90(blank)

빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

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

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L92{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

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

원자적 갱신 또는 exclusive transaction에 참여한다. 성공 값뿐 아니라 acquire/release ordering과 retry 조건을 앞뒤 줄에서 확인한다.

L94}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L95#define arch_atomic_fetch_add arch_atomic_fetch_add

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L96(blank)

빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

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

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L98(blank)

빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

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

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L100{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

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

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

L102}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L103#define arch_atomic_cmpxchg arch_atomic_cmpxchg

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L104(blank)

빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

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

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L106{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

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

이 함수가 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L108}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L109#define arch_atomic_try_cmpxchg arch_atomic_try_cmpxchg

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L110(blank)

빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

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

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L112{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

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

이 함수가 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L114}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L115#define arch_atomic_xchg arch_atomic_xchg

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L116(blank)

빈 줄은 x86-64 Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

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

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L118{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

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

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

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

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

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

L92 register c_type ret; \

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L93 __asm__ __volatile__ ( \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

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

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

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

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L96 : "r" (I) \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L97 : "memory"); \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L98 return ret; \

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L99} \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L100static __always_inline \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L101c_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를 분리해 해석한다.

L102{ \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L103 register c_type ret; \

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L104 __asm__ __volatile__ ( \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

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

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

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

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L107 : "r" (I) \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L108 : "memory"); \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L109 return ret; \

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L110}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L111(blank)

빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L112#define ATOMIC_OP_RETURN(op, asm_op, c_op, I, asm_type, c_type, prefix) \

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L113static __always_inline \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L114c_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를 분리해 해석한다.

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

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L116{ \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L117 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가 정리됐는지 확인한다.

L118} \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L119static __always_inline \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L120c_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를 분리해 해석한다.

L121{ \

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L122 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가 정리됐는지 확인한다.

L123}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L124(blank)

빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L125#ifdef CONFIG_GENERIC_ATOMIC64

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

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

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L127 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를 분리해 해석한다.

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

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L129#else

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

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

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L131 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를 분리해 해석한다.

L132 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를 분리해 해석한다.

L133 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를 분리해 해석한다.

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

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L135#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L136(blank)

빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L137ATOMIC_OPS(add, add, +, i)

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

L138ATOMIC_OPS(sub, add, +, -i)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L139(blank)

빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L140#define arch_atomic_add_return_relaxed arch_atomic_add_return_relaxed

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L141#define arch_atomic_sub_return_relaxed arch_atomic_sub_return_relaxed

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L142#define arch_atomic_add_return arch_atomic_add_return

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L143#define arch_atomic_sub_return arch_atomic_sub_return

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L144(blank)

빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L145#define arch_atomic_fetch_add_relaxed arch_atomic_fetch_add_relaxed

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L146#define arch_atomic_fetch_sub_relaxed arch_atomic_fetch_sub_relaxed

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L147#define arch_atomic_fetch_add arch_atomic_fetch_add

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L148#define arch_atomic_fetch_sub arch_atomic_fetch_sub

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L149(blank)

빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L150#ifndef CONFIG_GENERIC_ATOMIC64

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L151#define arch_atomic64_add_return_relaxed arch_atomic64_add_return_relaxed

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L152#define arch_atomic64_sub_return_relaxed arch_atomic64_sub_return_relaxed

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L153#define arch_atomic64_add_return arch_atomic64_add_return

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L154#define arch_atomic64_sub_return arch_atomic64_sub_return

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L155(blank)

빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L156#define arch_atomic64_fetch_add_relaxed arch_atomic64_fetch_add_relaxed

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L157#define arch_atomic64_fetch_sub_relaxed arch_atomic64_fetch_sub_relaxed

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L158#define arch_atomic64_fetch_add arch_atomic64_fetch_add

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L159#define arch_atomic64_fetch_sub arch_atomic64_fetch_sub

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L160#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L161(blank)

빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L162#undef ATOMIC_OPS

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L163(blank)

빈 줄은 RISC-V Atomic RMW와 spinlock: LSE/LL-SC, LOCK과 AMO 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L164#ifdef CONFIG_GENERIC_ATOMIC64

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

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

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

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

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

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 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.