18.1.2.3 스레드(Thread) 컨텍스트와 인터럽트 서비스 루틴(ISR) 컨텍스트 간의 안전한 통신 보장
비행 제어 시스템의 핵심 하드웨어 칩셋(예: STM32 레지스터 기반의 픽스호크)에서 실행되는 소프트웨어 실행 흐름은 크게 두 개의 문맥(Context)으로 나뉜다. 하나는 OS 커널 스케줄러에 의해 시분할(Time-slicing)로 제어되는 일반 스레드(Thread) 컨텍스트이고, 다른 하나는 하드웨어 이벤트(타이머 만료, 센서 데이터 수신 핀 트리거 등)에 맞춰 즉각적이고 독점적으로 실행되는 인터럽트 서비스 루틴(ISR, Interrupt Service Routine) 이다.
1. 통신 데드락과 스레드 락(Lock)의 공포
일반적인 다중 스레드 환경에서 데이터 무결성(Data Integrity)을 지키기 위해서는 임계 구역(Critical Section)에 뮤텍스(Mutex)나 세마포어(Semaphore)와 같은 락(Lock)을 걸어 보호한다. 그러나 ISR 내부에서는 운영체제의 블로킹 대기(Sleep/Wait) 메커니즘을 절대로 사용할 수 없다는 치명적인 제약이 존재한다.
만약 일반 제어기 스레드가 uORB 버퍼의 데이터를 읽는 도중에 락을 잠갔는데, 그 찰나의 순간에 자이로 센서 하드웨어 인터럽트(ISR)가 터져 새 데이터를 쓰려고 들면 어떻게 될까? 인터럽트는 락이 풀릴 때까지 대기(Wait)할 수 없기 때문에 시스템은 커널 패닉(Kernel Panic)을 일으키고 그대로 멈추게 된다.
2. PX4의 해법: Lock-Free와 락 안전 동기화 메커니즘
PX4는 uORB가 일반 백그라운드 스레드에서만 사용되는 것이 아니라, 극한의 응답 속도를 요하는 센서 ISR에서도 곧바로 호출될 수 있어야 한다는 철학을 고수했다. 그 결과, uORB는 인터럽트와 스레드가 서로의 실행을 방해하지 않고도 데이터를 주고받을 수 있는 비차단 인터럽트-안전(Interrupt-safe) 동기화 파이프라인을 개척했다.
2.1 인터럽트 마스킹(Interrupt Masking)을 통한 원자성 획득
발행자(Publisher)가 데이터를 쓸 때, uORB 코어는 irqsave() 계열의 함수를 호출하여 아주 미세한 순간(실제 메모리를 복사하고 포인터를 갱신하는 몇 마이크로초 단위) 동안 인터럽트를 비활성화(Masking)한다. 이는 OS 수준의 락(Lock)이 아니며, 극도로 짧은 사이클 안에서만 하드웨어적인 방해를 근원적으로 차단한다. 쓰기가 완료되면 즉시 irqrestore()를 호출해 시스템 인터럽트를 정상화한다.
2.2 세대 카운터(Generation Counter)를 활용한 락프리 읽기(Lock-Free Read)
구독자(Subscriber) 스레드는 데이터를 복사할 때 락을 획득하지 않는다. 대신, 읽기를 시작하기 전 현재 링 버퍼의 “세대(Generation) 숫자“를 기억해두고 메모리를 카피한다. 데이터 복사가 끝난 후 다시 세대 숫자를 확인하여 값이 만약 바뀌었다면(즉, 자신이 읽는 도중에 다른 ISR이 덮어쓰고 지나갔다면) 방금 읽은 데이터를 폐기하고 다시 시도(Retry)한다. (이러한 기법을 Seqlock과 유사한 동시성 제어 기법이라 부른다.)
3. 결론적 이점
이러한 코어 아키텍처 덕분에 자이로스코프 같은 초고속 센서는 데이터를 수신하자마자 하드웨어 인터럽트 문맥(ISR Context)을 벗어나기도 전에 즉각 orb_publish()를 날릴 수 있다.
운영체제의 스케줄링 대기열(Run Queue)에 등록 후 스레드로 스위칭되는 긴 지연 시간을 회피함으로써, 유저들은 펌웨어를 수정할 필요 없이 PX4의 미들웨어 레벨부터 확고하게 보장되는 결정론적인 최단 경로(Shortest Data Path) 를 향유하게 된다. 이것이 상업용 범용 미들웨어들이 결코 흉내 낼 수 없는 극강의 비행 안정성 특성이다.