18.5.1.3. 초기 세대 카운터 동기화: 발행 전 구독 시 로컬 카운터 초기화 로직의 엣지 케이스(Edge Case) 처리

18.5.1.3. 초기 세대 카운터 동기화: 발행 전 구독 시 로컬 카운터 초기화 로직의 엣지 케이스(Edge Case) 처리

PX4-Autopilot의 uORB 통신 메커니즘에서 데이터 신선도(Freshness)와 무결성을 보장하는 핵심 지표는 세대 카운터(Generation Counter, generation)이다. 발행자(Publisher)가 새로운 데이터를 링 버퍼에 기록할 때마다 노드의 전역 카운터가 증가하며, 구독자(Subscriber)는 자신이 마지막으로 읽었던 로컬 카운터와 전역 카운터를 비교하여 신규 데이터의 도달 여부를 판단한다. 본 절에서는 운영체제 부팅 직후 또는 모듈 초기화 단계에서 발생할 수 있는 “발행 전 구독(Subscribe-before-publish)“이라는 엣지 케이스(Edge Case)와 관련된 초기 카운터 동기화 로직을 C++ 구현 관점에서 심층 분석한다.

1. 세대 카운터 기반 동기화의 원리

uORB 매니저에서 orb_check() 함수가 호출되면, 내부적으로 매우 단순하지만 강력한 시간 복잡도 O(1) 수준의 락프리(Lock-free) 비교가 일어난다.

// 개념적 로직 (Conceptual Logic)
bool is_updated = (subscriber->local_generation < uORBDeviceNode->global_generation);

이 매커니즘이 완벽하게 동작하기 위해서는 구독자가 최초로 토픽 버퍼에 연결(open)될 때, 로컬 카운터(local_generation)의 초기값이 어떻게 설정되느냐가 시스템의 동작 방향을 결정하는 매우 중요한 요소로 작용한다.

2. 엣지 케이스: 발행 전 구독 현상 (Subscribe-before-Publish)

멀티 스레드 기반의 시스템 특성 상, 센서를 관장하는 드라이버보다 이를 수신해야 하는 제어기(Controller) 모듈이 먼저 구동되거나 초기화될 확률이 존재한다. 이렇게 되면 구독자는 토픽이 비어 있거나 아직 발행자(Publisher) 노드가 존재하지 않는 상태에서 일방적으로 orb_subscribe를 호출하게 된다.

이러한 지연 구독(Lazy Subscription) 상태에서 uORB 시스템은 다음 두 가지 극단적인 상황을 방지하기 위해 로컬 카운터 초기화 로직을 정교하게 제어한다.

2.1 허위 양성 (False Positive) 방어

만약 노드 버퍼에 과거 데이터가 10번(global_generation = 10) 누적 기록되어 있었다고 가정하자. 늦게 진입한 새로운 구독자의 local_generation이 무조건 0으로 초기화된다면, 해당 구독자는 진입 즉시 이 10건의 구형(Old) 데이터가 방금 새롭게 갱신된 것이라 착각하고 오래된 쓰레기값을 제어 연산에 투입할 위험이 있다.
오직 구독 시작 시점 이후에 발행된 데이터만이 제어기의 관심 대상이므로, 이 허위 양성은 치명적이다.

2.2 허위 음성 (False Negative) 방어 및 데드락 방지

아직 어떤 발행자도 없이 초기 생성된 깡통 노드의 경우 global_generation은 보통 0 상태이다. 이때 구독자의 로컬 카운터를 동일하게 0으로 맞추어 동기화해야, 훗날 발행자가 데이터를 처음 인가하여 전역 카운터가 1이 되는 순간 비로소 orb_check 조건절이 성립하여 상태 갱신을 원활히 감지할 수 있다.

3. uORB 메커니즘의 해결책 (Initialization Logic)

결론적으로 PX4-Autopilot 내 uORBDeviceNode::open() 구현부는 구독자의 참조(Reference)가 연결되는 파일 할당 순간에, 구독자 전용 객체(Subscriber instance) 내부의 로컬 카운터를 **발행 노드의 현재 전역 카운터와 강제로 동일하게 동기화(Sync)**한다.

stateDiagram-v2
    [*] --> Node_Created: orb_subscribe 호출
    Node_Created --> Sync_Counter: Subscriber 인스턴스 힙 할당
    Sync_Counter --> Waiting_Data: 로컬 카운터 = 전역 카운터(동기화)
    
    Waiting_Data --> Data_Matched: orb_check() 폴링
    Data_Matched --> Waiting_Data: (로컬 == 전역) -> 신규 데이터 없음(false)
    
    Waiting_Data --> Data_Updated: 발행자가 데이터 Push (전역 ++1)
    Data_Updated --> Fetch_Data: orb_check()가 true 반환
    Fetch_Data --> Waiting_Data: 데이터 복사 후 로컬 카운터 갱신

이러한 즉시 동기화(Immediate Sync) 원칙 덕분에 구독자는 자신이 파이프라인에 조인(Join)하기 직전까지 벌어졌던 발행 이력에 대해서는 깔끔하게 건너뛰고(Skip), 조인 직후 새롭게 도착하는 첫 번째 데이터부터만 정확한 갱신 이벤트 시그널을 수신할 수 있게 된다.

초기 세대 카운터의 무결성 동기화 설계는 멀티로터, 고정익 등 비행 기종이나 개별 모듈의 비동기적 실행 순서(Non-deterministic Timing)에 영향을 받지 않고 안심하고 사용할 수 있는 확정적(Deterministic) 제어 파이프라인을 다지며, PX4 브로커 아키텍처의 견고함을 뒷받침한다.