18.7.3.1. `logger` 모듈의 동적 구독 생성 및 uORB 메시지 페이로드를 SD 카드 바이너리(ULog) 포맷으로 직렬화하는 과정

18.7.3.1. logger 모듈의 동적 구독 생성 및 uORB 메시지 페이로드를 SD 카드 바이너리(ULog) 포맷으로 직렬화하는 과정

PX4-Autopilot 비행 제어 시스템의 로깅 아키텍처는 거대한 대역폭의 비행 이력 데이터를 널리 쓰이는 비효율적인 텍스트(.csv, .json 등)가 아닌 독자적인 고집적 바이너리 구조인 ULog(.ulg) 포맷으로 압축 기록한다. 이 아키텍처는 저가형 임베디드 SD 카드의 느린 I/O 쓰기 속도 물리적 한계를 정면으로 타파하고 디스크 용량 공간을 비약적으로 절약하는 일등 공신이다. 본 절에서는 logger 백그라운드 프로세스 데몬이 어떻게 런타임(Runtime)에 uORB 토픽(Topic)들을 동적으로 구독(Dynamic Subscription)하고, 도달하는 수많은 페이로드 메모리 덩어리들을 ULog 규격에 맞추어 초연하게 직렬화(Serialization)해내는지 그 핵심 파이프라인(Pipeline)을 심층 분석한다.

1. 동적 구독(Dynamic Subscription) 체계의 완성

앞서 구조적 절계에서 언급했듯 logger 모듈 파트 코드는 시스템 컴파일 타임(Compile-time)에 모니터링할 모든 대상 토픽 명단 리스트를 무식하게 하드코딩(Hardcoding)하지 않는다. 대신, uORB 매니저 커널이 트리거해주는 노드 생성 이벤트(Node Advertising Event)를 콜백(Callback)이나 폴링(Polling)으로 묵묵히 리스닝(Listening)한다.

만약 부팅 중 혹은 비행 중 새로운 sensor_accel 토픽이 누군가(예: SPI 센서 드라이버)에 의해 최초 발행 선언(orb_advertise)되면, 로거 모듈 내부의 이벤트 핸들러에서는 다음과 같은 동적 구독(Dynamic Subscribe) 루틴이 즉각 이행된다.

  1. 메시지 포맷 ID 및 구조 파악: 로거는 VFS 노드 시스템 깊숙이 접근하여 메인 생성자가 던진 이 토픽의 메타데이터(orb_metadata)를 긁어오며, 객체 구조가 품고 있는 16비트 고유 해시 메시지 ID 번호를 발급받는다.
  2. 구독 객체 리스트 추가 바인딩: 내부 벡터 형식으로 관리되는 LoggerSubscription 큐(Queue) 배열에 해당 토픽만을 전담 마크하기 위한 전용 C++ 구독 객체를 힙(Heap) 영역에 동적 할당하여 편입시키고, VFS 레벨의 통신 파일 디스크립터(fd)를 안전하게 열어 둔다.
  3. ULog 헤더에 포맷 정의 기록 (가장 중요): 본격적인 무지성 데이터를 수집하기 직전에, 로거는 펜을 들어 **“앞으로 파일 중반부부터 내가 쏟아낼 바이너리 숫자 덩어리 데이터 로그는 이러한 C 구조체 패킹 룰을 가진다”**라는 레이아웃 정보 메시지(FORMAT 메시지)를 ULog 파일의 완전 앞단(Header)에 선제적으로 기록해 가둬둔다.

이러한 동적 접근 런타임 스닙(Sniff) 로직 덕분에, 후발 연구자가 하드웨어 제어기를 커스텀 확장 조립하여 기존에 없던 “드론 파라슈트(낙하산) 전개 액추에이터 상태” 토픽을 맘대로 추가 생성하더라도 로거는 시스템 수정 없이 알아서 이를 다형적으로 인지하고 로깅 명단 인터페이스에 자동 취합시킨다.

2. ULog 포맷 직렬화(Serialization) 코어 매커니즘 및 철학

수학적으로 수백 개의 구독 객체 스레드들이 orb_copy 시스템 콜을 통해 토픽 메모리 배열을 로컬 로거 버퍼로 퍼올리기 시작하면, 이들은 즉시 ULog 청크(Chunk) 포맷으로 안전하게 직렬화되어 백엔드 큐에 쌓여야 한다.

놀랍게도 ULog 직렬화 변환 과정에는 protobuf 계열 프레임워크나 JSON과 같은 무겁고 클럭을 잡아먹는 트랜스포머 라이브러리가 아예 존재하지 않는다. uORB 계층의 태생적 구조체 메모리 레이아웃(struct alignment) 자체가 별다른 인코딩 없이도 이미 물리적으로 고도로 밀집된 C-Struct 타이트 패킹(Packing) 규격을 완벽히 따르고 있기 때문이다.

// logger 프로세스의 ULog 직렬화 파이프라인 (개념적 C++ 로직 재구성)
void Logger::write_message(const LoggerSubscription& sub, const void* data_payload) {
    // 1. ULog 메시지 미니 헤더 조립 (단 3바이트의 압도적 가벼움)
    struct {
        uint16_t msg_size; // 이 뒤에 따라올 물리적 페이로드의 바이트 크기
        uint8_t msg_type;  // 'D' (Data message)를 의미하는 아스키 코드 문자
    } header;
    
    // 2. 메시지 고유 식별 ID 추출
    uint16_t msg_id = sub.get_msg_id();
    
    // 3. 메인 백그라운드 로깅 링 버퍼에 순차적 직렬화 물리 복사 (Zero-cost Serialization)
    _writer.write_to_buffer(&header, sizeof(header));          // 헤더 3바이트 투척
    _writer.write_to_buffer(&msg_id, sizeof(msg_id));          // ID 2바이트 투척
    _writer.write_to_buffer(data_payload, sub.get_payload_size()); // 실제 페이로드 본체 투척 (Raw Byte Memcpy)
}

위 로직 블록 코드가 우아하게 증명하듯, 직렬화(Serialization) 과정은 단지 데이터의 구분을 위한 불과 5바이트짜리(사이즈+타입+ID) 미니멀 헤더를 덧붙인 뒤, 원래의 시스템 메모리 블록을 가공이나 엔디안 변환 없이 날것(Raw bytes) 그대로 memcpy로 밀어버리는 행위에 불과하다.

3. 메타데이터 주도(Metadata-Driven) 자가 기술 로그 아키텍처

이처럼 엔지니어링 적으로 구조체를 아무 설명 없이 무식하게 덤프(Dump) 복사해버리면 나중에 분석가가 하드디스크가 달린 PC 데스크톱에서 로그를 수학적으로 어떻게 분석할 수 있을까? 그 해답은 앞서 logger가 파일 입출력 생성 극초반부 헤더에 친절히 기록해둔 FORMAT 초기화 메시지 사전에 있다.
ULog 파일 자체의 설계 규격 내부에 데이터 사전(Dictionary) 이 완벽히 포함되어 자초지종을 독립적으로 설명해주는 Self-describing(자가 기술) 내장 구조이기 때문에, PC 환경의 Python 오픈소스 해석기 라이브러리(pyulog 등)는 이 .ulg 파일을 열자마자 사전 영역부터 파싱하여 판독해 내고, “아, ID 4번 데이터 청크는 uint64_t timestamp (8바이트)float32 xyz (4x3=12바이트) 배열로 구성된 총 20바이트 레고 블록이구나“라고 동적으로 완벽히 런타임 캐스팅 리버스 파싱 해낸다.

이 압도적인 영리함과 경량화 타협 설계 덕분에, PX4-Autopilot은 비행 중 보드 메인 CPU의 직렬화 파싱 연산 비용(Serialization Overhead Cost)을 이론상 제로(0)에 극한으로 수렴시키면서도, 거대한 수십 MB/s 대역폭 볼륨의 자율 주행 고주파 데이터를 어떠한 파편화 손실 없이 SD 카드라는 느린 그릇에 무사히 압착시켜 저장해 내는 시스템 공학의 기염을 토한다.