18.4.3. 대기열(Queue) 기반 데이터 이력(History) 관리 시스템 (orb_advertise_queue)
가장 기초적인 마이크로프로세서 간 uORB 토픽 통신 모델은 본질적으로 배열의 크기가 1인, 즉 “가장 최신의 단일 상태(State)만을 덮어쓰기 유지하는 싱글 버퍼(Single Buffer)” 형태(예: orb_advertise 기초 API)를 띤다. 고속으로 피드백되는 자이로 센서 데이터나 EKF 제어 상태 값의 경우, 이미 지나가 버린 과거의 데이터 값보다는 현재 0.001초 시점의 최신 결괏값이 제어 추종에 압도적으로 중요하기 때문이다.
그러나 비행 중 예측 불허하게 발생하는 이벤트성 로그, 예를 들어 텍스트 경고 메시지 구조체(mavlink_log_s)나 시스템 하드웨어 칩셋 에러 코드 배열의 경우, 단 하나라도 통신망에서 누락(Drop)되면 훗날 사고 원인 트리(Crash Tree) 분석 자체가 불가능해지는 치명적인 설계 결함을 낳는다. 이러한 극단적 양립의 환경에서 **“단 1바이트의 누락도 없는 순차 데이터 이력(History)의 보장”**을 위해, PX4 VFS 커널은 orb_advertise_queue라는 강력하게 확장된 API 파이프라인을 제공하여, 가상 토픽 노드 내부에 다중 깊이(Depth)를 가지는 히스토리 큐(History Queue) 메모리 시스템을 동적 구축해 낸다.
1. 상태(State) 지향 생태계 vs 이벤트(Event) 지향 생태계의 분리
일반적인 상태성 토픽(예: 기체의 자세 각도 vehicle_attitude)은 시스템 부하로 인해 하위 수신자(Subscriber) 스레드가 잠시 CPU 스케줄링을 양보당하여 10번의 하드웨어 업데이트를 놓쳤다 하더라도, OS가 11번째로 최신 업데이트를 강제로 읽어 들였을 때 여전히 계산을 속행하여 기체를 제어 및 생존시킬 수 있다. 구시대적 지식 타이밍은 이미 수학적으로 쓸모가 없기 때문이다.
하지만 일회성 이벤트성 토픽(예: 배터리 전압이 1차 임계치를 넘기며 발송된 3번의 경고)은 궤를 완전히 달리한다. 만약 지상국 MAVLink 송신 스레드가 이 3번의 시계열 순차 알람 중 앞선 첫 2개를 CPU가 늦게 깨는 바람에 읽지 못한 채, 이미 3번째 알람으로 마구 덮어써 진 싱글 메모리 방갈로만 퍼올린다면 어떻게 될까? 조종사와 GCS 시스템은 부저를 울려야 할 “Low Battery” 1차 안전 경고를 아예 보지 못한 채 갑작스럽고 치명적인 마지막 “Critical Battery Motor 컷오프” 메시지만 우연히 마주하고 드론이 속수무책으로 추락하는 것을 멍하니 지켜봐야 할 뿐이다.
orb_advertise_queue 로직은 최초 노드 개통 시점에 컴파일러가 큐의 깊이(예: 10개)를 명시적으로 인자로 고정 지정함으로써, 수신자 프로세스가 멀티테스킹 지연으로 극도로 게으르더라도 가장 과거 10번의 통신 기록 메모리가 허무하게 휘발되지 않고 순차 배열망에 온전히 락프리하게 잔존할 수 있도록 파괴적 보험(Physical Insurance)을 든다.
2. 다중 링 버퍼 아키텍처의 힙 메모리 스케일링(Scaling) 탄생
이중 C++ API 추상화(orb_advertise_queue 호출)가 시스템 인가되면, 하위 VFS 공간의 uORBDeviceNode 객체는 탄생하는 init 시점에 자신이 지정받은 큐 사이즈(_queue_size) 변수만큼 자신의 내부 RAM 힙(Heap) 메모리 할당 포인터 용량을 물리적으로 뻥튀기(Expansion) 거대화한다.
// 큐(Queue) 통신을 전폭 지원하는 uORB 노드의 논리적 메모리 배열 할당 스케일링 디자인
size_t single_struct_size = orb_meta_size; // 단일 구조체 페이로드 1개의 크기
size_t queue_capacity = user_requested_depth; // 유지할 이력 슬롯의 개수
// 히스토리를 잃어버리지 않을 통짜 덩어리의 연속된 힙(Heap) 물리 메모리 동적 할당
size_t total_buffer_size = single_struct_size * queue_capacity;
_buffer = (uint8_t*) malloc(total_buffer_size);
이 물리 구조는 본질적으로 1차원의 단순한 데이터 방갈로가 아닌 **2차원 매트릭스 성격의 링 버퍼(Ring Buffer Array)**를 커널 힙 시스템 상에 입체적으로 전개하는 것이다. 단일 메모리 주소에 앞선 데이터를 잔인하게 덮어쓰던 과거의 단칸방에서 완벽히 벗어나, 이제 N개의 데이터 객체가 시계열 순서표 인덱스를 쥐고 차례대로 물리 메모리 오프셋(Offset) 방에 합법적으로 일시 입주하게 된다.
3. 읽기(Read)의 차원 대이동: 시공간을 거슬러 오르는 스캐닝
가상 큐 버퍼 시스템이 시스템 아키텍처에 정식 도입됨에 따라 물리적 구독자(Subscriber) 스레드의 오프셋 읽기 하위 전략 또한 근본적으로 궤를 파괴하며 달리하게 된다. 단일 싱글 버퍼 시절의 orb_copy 호출이 맹목적으로 눈 감고 0번 위치 현재 방의 데이터만 통째로 투기하듯 훔쳐오는 무식한 memcpy 행위였다면, 큐 기반 링 버퍼 체계에서의 구독자는 자신이 “과거에 정확히 언제 텍스트를 어디까지 읽었는지“를 꼼꼼히 기록한 유저별 개인용 책갈피(generation_counter)를 들이밀고 수치 역산을 통해 과거로 타임머신을 타고 역이동해야 한다.
예컨대 구독자가 슬립(Sleep)에서 10 밀리초(ms) 만에 더디게 깨어나 자신의 로컬 낡은 구조체 책갈피와 커널 노드의 최신 전역 책갈피 값을 수학적으로 비교해 보았더니 5만큼 차이가 난다고 가정하자. 시스템은 아직 타 스레드에 의해 완전히 지워지지 않은 링 버퍼의 과거 배열 메모리 오프셋을 역산 추적하여, 해당 망각한 구독자 스레드가 바보처럼 놓쳤던 정확히 5단계 전의 오래된 메시지 인덱스부터 최신 배열 인덱스까지 시계열을 따라 차례차례 순차 배달(pop mapping)해 주며 무결성을 아름답게 수호해 낸다.
결론적으로 orb_advertise_queue 체제 시스템은, 하드웨어 성능이 제한된 매우 엄격한 하드 리얼타임 OS(NuttX RTOS) 임베디드 환경 하에서, 스케줄링 지연(Context Switching Latency)으로 인해 필연적으로 발생하는 이기종 스레드 간의 비극적 타이밍 엇갈림 손실을, 찰나의 “시간적 여유 부재“를 광활한 “공간적 메모리(RAM)의 확장 궤도“로 거침없이 역치환하여 동시성 투쟁의 피를 흘리지 않고 우아하게 극복해 내는 시공간 추상화의 가장 위대한 공학적 변곡점이라 할 수 있다.