18.6.3 uORB::SubscriptionCallback과 PX4 Work Queue 시스템의 결합
px4_poll() 기반의 멀티플렉싱(18.6.1절)은 훌륭한 비동기 메커니즘이지만 태생적인 설계 한계를 지니고 있다. 바로 “각 구독 모듈마다 최소 한 개의 전담 스레드(Thread)가 필요하다” 는 점이다.
수십 개가 넘는 PX4의 자잘한 제어 및 모니터링 모듈들에 각각 전용 스레드를 할당하면, 아무리 가벼운 NuttX RTOS라도 스레드마다 할당되는 스택 메모리(최소 2KB~8KB)가 누적되어 물리적 RAM 용량을 금세 다 써버리고 만다.
1. 스레드 풀(Thread Pool) 기반의 Work Queue 설계
PX4는 메모리 절약을 위해 다수의 독립 스레드 배치를 포기하고, 극소수의 작업자 스레드(Worker Thread)들로 수십 개의 모듈 알고리즘을 번갈아가며 실행하는 워크 큐(Work Queue) 시스템을 전격 도입했다. (예: wq:rate_ctrl, wq:nav_and_cmds 등).
이 아키텍처에서 특정 모듈은 독립적인 스레드가 아니라 하나의 ’워크 아이템(Work Item)’으로 간주된다. 문제는 이 워크 아이템들이 어떻게 “언제 내가 큐에 올라타서 실행되어야 하는가?” 를 스케줄러에게 알려주느냐다. 여기서 uORB와 워크 큐를 연결하는 가교 구실을 하는 것이 바로 uORB::SubscriptionCallbackWorkItem 클래스이다.
2. 콜백 브리지(Callback Bridge)의 동작 원리
개발자가 EKF 데이터가 갱신될 때마다 실행되어야 하는 제어 모듈을 SubscriptionCallbackWorkItem으로 감싸서 구현했다고 가정하자.
- 이 래퍼(Wrapper)는 초기화 시점에
uORBDeviceNode에 자신의 전용 콜백 함수 포인터를 은밀히 등록해 둔다. - 이 모듈은 평소에는 워크 큐 리스트에서 완전히 배제되어 잠들어 있다(Unscheduled).
- 퍼블리셔가 새로운 EKF 데이터를 링 버퍼에 쓰고
poll_notify()를 호출하는 순간(18.6.2절), 커널은 더 이상 세마포어 시그널을 날리지 않고 노드에 등록된 콜백 함수를 즉시 호출(Trigger) 해 버린다. - 이 콜백 함수 내부의 코드는 매우 단순하다. 자신을 감싸고 있던 모듈(워크 아이템)을 특정 작업자 스레드(Worker Thread)의 실행 대기열(Run List) 최하단에 슬그머니 밀어 넣는(Schedule) 역할만 수행한다.
- 해당 워크 큐를 관리하는 작업자 스레드가 깨어나 리스트를 훑어보다가, 방금 큐에 들어온 이 모듈의
Run()메서드를 호출하여 제어 알고리즘을 실행시킨다.
이 완벽한 결합을 통해 PX4는 무거운 스레드 생성 오버헤드 없이, 메모리 사용량을 극한으로 억압(RAM Footprint 극소화)하면서도 센서 데이터가 도착하는 즉시 모듈을 스케줄링해 내는 궁극적인 이벤트 구동(Event-Driven) 생태계를 완성하게 되었다. 오늘날 작성되는 거의 모든 최신 PX4 모듈(ModuleBase 상속 구조)은 바로 이 기반 위에서 동작한다.