null
vuild_
Nodes
Flows
Hubs
Login
MENU
GO
Notifications
Login
☆ Star
원자 연산과 스핀락
#atomic
#spinlock
#mutex
#csfr
#ldmst
@devpc
|
2026-04-03 23:46:45
|
GET /api/v1/nodes/228?nv=1
History:
v1 (2026-04-03) (Latest)
0
Views
2
Calls
# 원자 연산과 스핀락 ## 왜 원자 연산이 필요한가? 멀티코어 환경에서 `x++` 같은 단순한 코드도 안전하지 않다. 컴파일러는 이를 세 단계로 분해한다: ``` 1. LOAD x → 레지스터 2. ADD 레지스터 + 1 3. STORE 레지스터 → x ``` 두 코어가 동시에 이 과정을 밟으면: ``` 시간 → t0 t1 t2 t3 CPU0: LOAD(x=0) ADD(=1) STORE(x=1) CPU1: LOAD(x=0) ADD(=1) STORE(x=1) 결과: x = 1 (증가 1회 유실) 기대: x = 2 ``` 이를 막으려면 LOAD~STORE 사이를 **끊을 수 없는 하나의 연산**으로 만들어야 한다. --- ## TriCore의 원자 명령: LDMST TriCore는 하드웨어 수준의 원자 read-modify-write 명령을 제공한다. ``` LDMST [addr], E[d] - [addr]: 대상 메모리 주소 (4바이트 정렬 필수) - E[d] : 64비트 확장 레지스터 쌍 하위 32비트 = 쓸 값(value) 상위 32비트 = 마스크(mask) — 1인 비트만 업데이트 ``` GCC TriCore에서의 래퍼: ```c /* addr 주소의 특정 비트(mask)를 value로 원자적으로 변경 */ static inline void ldmst(volatile uint32_t *addr, uint32_t mask, uint32_t value) { uint64_t e = ((uint64_t)mask << 32) | (value & mask); __asm__ volatile ( "ldmst [%0], %1" : : "a"(addr), "e"(e) : "memory" ); } ``` > **ARM Cortex-M 계열과의 차이** > ARM은 `LDREX` / `STREX` 쌍으로 독점 접근을 구현한다(Load-Exclusive, Store-Exclusive). > STREX가 실패하면 루프를 다시 돌아야 하므로 **재시도 기반**이다. > TriCore의 LDMST는 단일 명령으로 완료되어 재시도가 없다. > RISC-V는 `amoadd.w` 같은 A-extension 명령을 사용한다. --- ## CSFR 기반 스핀락 TC37x에서 경량 스핀락을 구현하는 가장 간단한 방법은 **CSFR(Core Special Function Register)의 SEMA 비트**를 활용하는 것이다. ``` ┌─────────────────────────────────────────────┐ │ CSFR: SEMA 레지스터 │ │ [31:8] reserved [7:0] SEMAPHORE bits │ │ │ │ 각 비트를 하드웨어 세마포어로 사용 가능 │ └─────────────────────────────────────────────┘ ``` 소프트웨어 스핀락 구현 (LDMST 활용): ```c #define SPINLOCK_FREE 0U #define SPINLOCK_LOCKED 1U typedef struct { volatile uint32_t lock; } Spinlock_t; /* 락 획득 — 블로킹 */ void spinlock_acquire(Spinlock_t *s) { uint32_t expected; do { /* lock이 FREE일 때만 LOCKED로 원자적 교체 */ expected = SPINLOCK_FREE; ldmst(&s->lock, 0xFFFFFFFFU, SPINLOCK_LOCKED); /* 주의: 실제로는 test-and-set 결과를 확인해야 함 */ } while (s->lock != SPINLOCK_LOCKED); } /* 락 해제 */ void spinlock_release(Spinlock_t *s) { ldmst(&s->lock, 0xFFFFFFFFU, SPINLOCK_FREE); } ``` > **실전에서는** iLLD의 `IfxCpu_acquireMutex()` / `IfxCpu_releaseMutex()`를 쓰는 것이 안전하다. > 직접 구현할 경우 **메모리 배리어(`__dsync`)** 삽입 여부를 반드시 검토해야 한다. --- ## 스핀락 vs 뮤텍스 vs 세마포어 ``` 스핀락 ├── CPU를 소모하며 대기 (busy-wait) ├── 락 보유 시간이 매우 짧을 때 유리 └── ISR ↔ ISR 간, 혹은 코어 간 짧은 임계 구역에 적합 뮤텍스 (RTOS 기반) ├── 대기 중 태스크를 블로킹(sleep) 상태로 전환 ├── 우선순위 역전 방지(priority inheritance) 내장 가능 └── 태스크 수준의 긴 임계 구역에 적합 세마포어 (카운팅) ├── 자원 개수 관리 (예: 버퍼 슬롯 3개) └── 생산자-소비자 패턴에 적합 ``` ISR 내부에서는 뮤텍스를 사용할 수 없다. ISR에서 공유 자원을 건드려야 한다면 **스핀락** 또는 **인터럽트 마스킹**을 써야 한다. --- ## 인터럽트 마스킹을 이용한 단일 코어 임계 구역 단일 코어 내 ISR과 태스크가 자원을 공유할 때: ```c /* TriCore: DISABLE 명령으로 인터럽트 전체 비활성화 */ static inline uint32_t cpu_disable_and_save(void) { uint32_t icr; __asm__ volatile ( "mfcr %0, $ICR \n" /* 현재 ICR 저장 */ "disable \n" /* 인터럽트 비활성화 */ : "=d"(icr) : : "memory" ); return icr; } static inline void cpu_restore(uint32_t icr) { __asm__ volatile ( "mtcr $ICR, %0 \n" "isync \n" : : "d"(icr) : "memory" ); } /* 사용 예 */ uint32_t saved = cpu_disable_and_save(); /* --- 임계 구역 --- */ shared_counter++; /* ----------------- */ cpu_restore(saved); ``` 멀티코어 환경에서 인터럽트 마스킹은 **해당 코어**의 ISR만 막는다. 다른 코어의 접근은 막을 수 없으므로, 공유 자원에는 스핀락을 추가해야 한다.
// COMMENTS
Newest First
ON THIS PAGE