21.7.1. C++ 래퍼(uORB::Subscription)를 활용한 메모리 복사 제어
uORB의 가장 원시적인 형태는 C 언어 기반의 거친 API(orb_subscribe, orb_copy)로 구성되어 있다.
과거 PX4 코드베이스에서는 이 C API를 직접 호출하기 위해 파일 디스크립터(File Descriptor, fd)를 열어젖히고 read() 시스템 콜을 날리듯이 구조체를 복사해 오는 낡은 통신 방식을 고수했다.
하지만 이 방식은 C++ 객체 지향으로 짜인 최신 모듈 아키텍처와는 전혀 어울리지 않았고, 포인터 캐스팅 실수로 인한 메모리 침범 버그가 빈번하게 발생했다.
이를 완벽하게 해결하기 위해 2018년 이후 PX4 코어팀은 모든 uORB 통신을 감싸는 똑똑하고 안전한 C++ 템플릿 래퍼(Wrapper), uORB::Subscription 패밀리를 탄생시켰다.
1. 원시 C API의 불안정성
전통적인 C 언어 방식으로 센서 데이터를 읽어오는 모습을 살펴보자.
// [X] 구시대의 원시 C API 통신 방식
int sensor_sub_fd = orb_subscribe(ORB_ID(sensor_gyro)); // 파일 디스크립터 발급
struct sensor_gyro_s gyro_data; // 데이터를 담을 빈 껍데기 박스 준비
bool updated;
orb_check(sensor_sub_fd, &updated); // 새 데이터가 왔는지 확인
if (updated) {
// 껍데기 박스에 구조체를 강제 복사!
// 만약 struct 크기가 달라지면 런타임에 쓰레기값이 채워진다.
orb_copy(ORB_ID(sensor_gyro), sensor_sub_fd, &gyro_data);
}
이 코드는 개발자가 디스크립터(fd) 번호를 까먹거나, 엉뚱한 구조체(sensor_accel_s 같은)의 포인터를 orb_copy에 들이밀더라도 컴파일러가 잡아내지 못한다는 치명적인 약점을 가지고 있다. 또한 매번 껍데기 변수를 지역 변수로 선언해야 해서 스택 메모리를 지저분하게 만든다.
2. uORB::Subscription의 스마트한 타입 강제(Type Enforcement)
최신 C++ 템플릿 래퍼를 사용하면, 구독자 객체를 선언하는 그 순간에 “내가 구독할 토픽의 구조체 타입“을 컴파일러의 멱살을 잡고 각인시켜 버릴 수 있다.
// [O] 현대적인 C++ 래퍼 클래스 방식
#include <uORB/Subscription.hpp>
class CustomApp {
private:
// 타입(sensor_gyro_s)을 템플릿 인자로 명시하여 극강의 자료형 방어를 구축함
uORB::Subscription _gyro_sub{ORB_ID(sensor_gyro)};
};
이렇게 선언해 두면, Run() 함수 안에서 데이터를 읽어오는 행위가 한 편의 시처럼 아름답고 간결해진다.
void CustomApp::Run() {
sensor_gyro_s gyro_data;
// update() 함수 하나로 orb_check와 orb_copy가 동시에, 안전하게 수행된다!
if (_gyro_sub.update(&gyro_data)) {
// 이 블록 안으로 들어왔다면, 새 데이터가 도착했고 gyro_data에 복사까지 완료된 상태다.
process_data(gyro_data.x, gyro_data.y, gyro_data.z);
}
}
_gyro_sub.update(&gyro_data) 메서드 내부에는 템플릿 매직이 걸려있기 때문에, 실수로 &accel_data 같은 다른 타입의 포인터를 밀어 넣으면 빌드가 그 자리에서 터지면서 개발자의 뺨을 때린다. 이 덕분에 캐스팅 오류로 비행 중 기체가 뒤집히는 참사는 시스템 레벨에서 원천 봉쇄되었다.
하지만 위 코드도 여전히 sensor_gyro_s gyro_data; 라는 복사본 통(Buffer)을 스택(Stack) 메모리에 임시로 잡아 두어야 한다는 한계를 지니고 있다.
만약 우리가 스택 메모리를 단 1바이트도 낭비하고 싶지 않은 강박증 환자라면? 이 통조차 클래스 내부에 완전히 영구 결합해 버리는 궁극의 템플릿, SubscriptionData<T> 의 진가를 다음 챕터(21.7.1.1)에서 확인해 보자.