18.5.1. 토픽 구독(`orb_subscribe`) 및 파일 디스크립터(FD) 연동

18.5.1. 토픽 구독(orb_subscribe) 및 파일 디스크립터(FD) 연동

PX4-Autopilot의 uORB 메커니즘에서 특정 토픽(데이터 스트림)을 수신하기 위한 첫 관문은 orb_subscribe() API를 호출하는 것이다. 이 함수는 센서 데이터나 상태 제어 명령을 비동기적으로(Asynchronously) 수신하려는 모듈(예: Position Controller, EKF 등)에서 가장 흔하게 활용된다. 본 절에서는 orb_subscribe의 내부 동작 원리와 하부 OS 계층(NuttX RTOS 또는 POSIX 기반 VFS)의 연동 방식을 소스 코드 관점에서 면밀하게 구조화한다. (본 분석은 PX4 펌웨어 최신 메인 브랜치를 따른다.)

1. orb_subscribe()의 역할과 구조적 파이프라인

orb_subscribe(const struct orb_metadata *meta) 함수는 인자로 주어지는 메타데이터(Topic 정보)를 바탕으로 uORB 매니저에 등록된 디바이스 노드를 식별하여 파일 디스크립터(File Descriptor, FD)를 반환한다. C++ 백엔드 로직에서는 이 함수가 최종적으로 uORB의 내부 노드 매니저인 DeviceNode 또는 uORB::Manager의 구독 메서드를 호출하도록 래핑(Wrapping)되어 있다.

전통적인 POSIX 프로그래밍에서 특정 파일에 접근하기 위해 open()을 호출하듯, uORB 역시 /obj/토픽이름 형태의 가상 파일 시스템(Virtual File System, VFS) 경로를 열어 그 핸들을 획득하는 구조를 띄고 있다.

2. VFS(가상 파일 시스템)와의 매핑 및 open() 시스템 콜

NuttX와 같이 POSIX 규격을 준수하는 실시간 운영체제에서, 모든 하드웨어 디바이스와 uORB 버퍼는 VFS 네임스페이스에 문자(Character) 디바이스 노드로 등록된다.

  1. 디바이스 경로 탐색(Device Path Resolution): orb_subscribe("sensor_accel")가 실행되면 내부적으로 /obj/sensor_accel0라는 가상 파일 경로로 해석된다. (다중 인스턴스의 경우 0, 1, 2 등의 접미사가 붙는다.)
  2. uORBDeviceNode::open() 호출 체인: OS는 VFS 핸들러를 타게 되어 최종적으로 uORB 코어가 구현한 디바이스 드라이버 로직, 즉 uORBDeviceNode::open(file *filp) 콜백으로 제어권을 넘긴다.
  3. 참조 카운팅(Reference Counting): 한 토픽에 여러 구독자가 접근할 수 있으므로, 내부적으로 링 버퍼에 대한 참조 카운팅(_subscriber_count 등)이 증가한다. 이 카운트에 기반하여 시스템은 사용 중인 메모리의 수명(Lifecycle) 관리를 적절하게 통제한다.

3. 구독자 리스트 관리 및 로컬 데이터 동기화

open() 시스템 콜을 통해 파일 디스크립터가 부여되는 시점에, uORB 노드는 해당 구독자의 로컬 상태 관리를 위한 기초 작업을 완료한다.

  • Subscriber(구독자) 구조체 할당: 구독자의 읽기 이력을 추적하기 위해 OS 레벨에서 할당된 메모리 풀 내부에 고유한 구조체(예: Subscriber 노드 정보)를 매핑시킨다.
  • 초기 세대 카운터 매핑(Generation Counter Sync): 데이터 무결성을 보장하는 핵심은 구독 시점의 최신 카운터 동기화이다. open 연산 직후 아직 읽지 않은 최신 데이터가 있다면 그 이력을 추적할 수 있도록 구독자 내부의 로컬 카운터를 초기화(또는 노드의 전역 카운터인 generation과 매칭) 해두는 과정을 포함한다. 이로 인해 구독 즉시 발생한 orb_check 연산은 O(1) 복잡도의 비교 오버헤드로 가장 새로운 메시지 유무를 신속하게 식별할 수 있다.
sequenceDiagram
    participant App as PX4 Application<br>(Estimator/Controller)
    participant uORB as uORB API(C)
    participant VFS as NuttX VFS
    participant Node as uORBDeviceNode(C++)

    App->>uORB: orb_subscribe(ORB_ID(topic))
    uORB->>VFS: open("/obj/topic0", O_RDONLY)
    VFS->>Node: uORBDeviceNode::open(file)
    Node-->>Node: 구독자 카운트 증가 & Subscriber 구조체 이력 초기화
    Node-->>VFS: File 구조체 초기화 반환
    VFS-->>uORB: 파일 디스크립터(fd >= 0) 반환
    uORB-->>App: fd (int)

4. 설계의 기술적 결론

요약하면, orb_subscribe()는 단순히 메모리 주소를 넘겨받는 인터페이스가 아니라, POSIX 범용성 위에 실시간성을 훼손하지 않고 다중 스레드의 읽기 동시성을 지원하기 위한 정교한 VFS 브릿지 역할을 수행한다. 비행 제어 모듈들은 반환된 파일 디스크립터를 통해 이후의 read(), poll(), ioctl() 등의 무거운 객체 참조 없이 파일 통신 연산을 실행함으로써, uORB 시스템과 완벽하게 디커플링(Decoupling)된 상태로 실시간 센서 및 제어 데이터를 소비할 수 있게 된다.