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 카운터에 매우 자주 사용됨