QNX RTOS: 4-11. Synchronization - Atomic Operations
2025. 11. 27. 17:07ㆍ운영체제/QNX
1. Atomic Operation이란?
Atomic Operation =
“32비트(또는 작은 크기) 값을 한 번의 불가분한 연산으로 update하는 기능”
보장하는 것:
- Preemption(스레드 선점) 중에도 연산이 쪼개지지 않음
- Interrupt 발생 시에도 중간 상태가 없음
- 여러 스레드가 동시에 같은 변수에 접근해도 안전함
즉, small-size shared memory를 다룰 때 mutex 없이도 thread-safe.
2. QNX가 제공하는 주요 Atomic 함수
모두 "원자적으로 수행"되며,
일부 함수는 연산 전 값을 반환(return original value) 하는 버전도 존재.
✔ atomic_add()
- 저장 위치에 값을 더함
- atomic_add_value()는 더하기 전의 원래 값을 반환
✔ atomic_sub()
- 저장 위치에서 값을 뺌
✔ atomic_set()
- 특정 비트를 OR로 세팅
✔ atomic_clear()
- 특정 비트를 AND NOT으로 clear
✔ atomic_toggle()
- XOR로 비트를 flip
이 모든 연산은 하나의 atomic CPU instruction 또는 equivalent loop로 구현됨.
3. 어디에 쓰나? (Mutex vs Atomic 비교)
Mutex vs Atomic:
항목MutexAtomic
| Blocking 여부 | Block 가능 (kernel call) | 절대 Block 없음 |
| 비용 | 비교적 무겁고 느림 | 아주 가벼움 |
| 보호 범위 | 임의 크기(구조체 포함) | 32bit/64bit의 작은 값 |
| 용도 | 복잡한 크리티컬 섹션 보호 | 카운터/플래그 등 단일 값 업데이트 |
즉,
구조체/큐 같은 큰 자료구조 → Mutex
단일 숫자 카운터/플래그 → Atomic
4. 예시: do_work() 함수의 은근한 버그
강의에서 언급한 이전 mutex 예제를 떠올려보자.
코드 흐름:
- var3를 ++한 뒤
- var3 % 10,000,000 == 0이면 printf()
문제:
멀티스레드 조건에서 다음과 같은 race condition 발생:
- var3 = 9,999,999
- 스레드 A: var3 = 10,000,000
- 스레드 B: var3 = 10,000,001
- 둘 다 값을 검사할 때 이미 10,000,001이므로
절대 10,000,000 조건을 만족하지 못함
→ 10M마다 찍혀야 할 printf가 영원히 안 찍히는 경우 발생
원인:
var3++가 원자적이 아니기 때문
(+1은 실제로 3단계: Load → Add → Store)
5. 해결 방법 1: Mutex로 보호 (하지만 무거움)
pthread_mutex_lock(&var3_mutex); var3++; if (var3 % 10000000 == 0) printf("..."); pthread_mutex_unlock(&var3_mutex);
단점:
- 매 iteration마다 mutex lock/unlock 비용 발생
- 경쟁(conflict)이 있으면 kernel call까지 수행 → 매우 느려짐
6. 해결 방법 2: atomic_add_value() 사용 (권장)
코드 예시
int old = atomic_add_value(&var3, 1); if ((old + 1) % 10000000 == 0) { printf("...\n"); }
이 방식의 장점:
- 원자성 보장 → 값이 사라지거나 덮여쓰이는 race 없음
- Lock-free → 높은 성능
- atomic_add_value는 “연산 전의 값”을 리턴해주므로
old 값 기반으로 정확하게 modulo 계산 가능
정확하게 작동하는 이유:
- 스레드 A가 old=9,999,999 → 새 값 = 10,000,000
- 스레드 B는 old=10,000,000 → 새 값 = 10,000,001
- 따라서 정확히 10,000,000에서 한 번은 반드시 printf 발생
7. QNX vs C11 Atomic
QNX 구성요소:
- atomic_add(), atomic_set(), atomic_clear() 등
매우 단순하고 사용 쉬움 - QNX OS에 최적화됨
C11 Atomic:
- atomic_fetch_add(), atomic_compare_exchange_strong() 등
- 더 portable(이식성 높음)
- memory_order 옵션까지 있어 복잡함
- 실무에서 간단한 카운터라면 QNX atomic을 더 자주 씀
8. Vehicle / RTOS 실전 인사이트
✔ (1) 차량 네트워크 스택에서의 카운터/플래그
예:
- 패킷 drop count
- RX/TX 이벤트 플래그
- Some/IP, TCP, DDS 패킷 통계
- ISR에서 increment하는 인터럽트 카운터
→ 모두 Mutex 쓰면 성능 바닥남, Atomic이 정답
✔ (2) Sensor Fusion Pipeline에서의 “frame id”
카메라/라이다가 frame number 증가시키는 용도
→ atomic_add 가 가장 빠르고 정확함
✔ (3) Multicore ZCU + HPC 구조에서 Lock Contention 감소
Lock이 얽히면 real-time 성능 떨어짐
→ 가능한 곳은 atomic으로 대체하여 latency 감소
✔ (4) Priority Inversion 문제와 무관
Atomic 연산은 lock-free이므로 priority inversion 자체가 발생하지 않음
9. 요약
- Atomic operations는 “32bit 값 하나”를 mutex 없이 안전하게 업데이트하는 기능
- Preemption/Interrupt에도 안전
- 카운터/플래그처럼 단일 값 업데이트에 최적화
- mutex보다 훨씬 빠르고 가벼움
- 멀티스레드 환경에서 race 없이 정확한 값을 유지할 수 있음
- QNX는 atomic_add_value 같은 편리한 함수 제공
- 차량/RTOS 개발에서 성능-critical 카운터에 매우 자주 사용됨
'운영체제 > QNX' 카테고리의 다른 글
| QNX RTOS: 5. Interprocess Communication (0) | 2025.11.27 |
|---|---|
| QNX RTOS: 4-11. Atomic Operations 실험 (0) | 2025.11.27 |
| QNX RTOS: 4-10. Conditional Variables 실험 (0) | 2025.11.27 |
| QNX RTOS: 4-10. Synchronization - Conditional Variables (0) | 2025.11.27 |
| QNX RTOS: 4-9. Mutexes 실험 (0) | 2025.11.27 |