18.2.3 uORB 메타데이터(`orb_metadata`) 블록의 구조와 컴파일 타임 생성

18.2.3 uORB 메타데이터(orb_metadata) 블록의 구조와 컴파일 타임 생성

지금까지 18.2.1절과 18.2.2절을 통해 .msg 텍스트 파일이 어떻게 패딩 없는 견고한 C++ 구조체(struct)로 변환되는지 레이아웃 관점에서 살펴보았다. 하지만 이 구조체만으로는 uORB 미들웨어가 통신망을 수립할 수 없다.

uORB 매니저(Core) 입장에서는 발행자(Publisher)가 어떠한 토픽을 개설하겠다고 요청했을 때, “이 토픽의 이름이 정확히 무엇인지, 메모리를 몇 바이트 할당해야 하는지, 내부에는 어떤 자료형들이 들어있는지” 를 알아야만 링 버퍼(Ring Buffer)를 준비할 수 있다.

문제는 C/C++ 언어의 태생적 한계인 리플렉션(Reflection, 프로그램이 실행 중에 자신의 구조체나 객체 내부 정보를 스스로 파악하는 기능)의 부재에 있다. 자바(Java)나 파이썬(Python)이었다면 객체를 조사하여 필드 정보를 실시간으로 얻어오겠지만, C++에서는 컴파일이 끝나는 순간 변수명과 크기 정보는 모두 기계어 주소로 치환되어 증발해 버린다.

1. 인공적인 리플렉션: orb_metadata 블록의 도입

이러한 언어적 한계를 극복하기 위해, PX4 빌드 파이프라인(px4_generate_messages.py)은 컴파일 타임(Compile-time)에 구조체 데이터와 더불어 인공적인 리플렉션 데이터 블록을 강제로 하드코딩(Hard-coding)하여 소스 파일(.cpp)에 찍어낸다. 이것이 바로 orb_metadata (uORB Meta Data) 구조체이다.

모든 .msg 파일이 변환될 때마다, 헤더 파일(.h)에는 토픽의 실제 데이터 구조체와 함께 다음과 같이 메타데이터를 가리키는 외부 전역 포인터(Extern Global Pointer)가 짝꿍처럼 선언된다.

/* 1. 토픽의 실제 데이터 구조체 */
struct vehicle_gps_position_s {
    uint64_t timestamp;
    int32_t lat;
    int32_t lon;
    // ...
} __attribute__((__packed__));

/* 2. 해당 토픽의 '신분증' 역할을 하는 메타데이터 블록 선언 */
extern const struct orb_metadata __orb_vehicle_gps_position;

2. orb_metadata의 역할: 토픽의 신분증(ID Card)

__orb_[토픽명] 블록은 비행 제어기 전역에서 유일하게 단 하나만 존재(Singleton)하는 상수(Constant) 데이터 공간이다. 여기에는 토픽의 문자열 이름("vehicle_gps_position"), 구조체의 정확한 컴파일 타임 바이트 크기(sizeof(struct vehicle_gps_position_s)), 그리고 로거(Logger)를 위한 내부 필드 포맷 스트링 등이 뺴곡히 기록되어 있다.

실제 비행 제어기(예: 센서 드라이버)에서 객체가 발행(Advertise)을 시작할 때 코드를 살펴보자.

// 발행자(Publisher)는 데이터가 아닌 이 '메타데이터의 주소값'을 코어로 넘긴다.
orb_advert_t gps_pub = orb_advertise(ORB_ID(vehicle_gps_position), &gps_data);

위 코드에서 ORB_ID() 매크로는 결국 앞서 보았던 &__orb_vehicle_gps_position 메모리 주소로 정확히 치환된다. uORB 코어 매니저는 단지 이 주소값을 넘겨받음으로써 런타임에 자신에게 들어온 데이터 페이로드(Payload)의 이름이 무엇인지, VFS(가상 파일 시스템)상에 몇 바이트의 노드를 생성해야 하는지를 완벽하게 파악하고 대응할 수 있게 된다.

다음 절부터는 이 orb_metadata를 구성하는 핵심 멤버 변수들의 역할과, 이 거대한 상수 블록을 짧은 한 줄로 압축해 내는 C 언어 매크로(Macro)의 흑마법에 대해 심층적으로 파헤칠 것이다.