19.9.3. 시스템 부하(Overhead)를 최소화하기 위한 Zero-copy 방식 고찰
마침내 PX4 uORB 아키텍처 대단원의 마지막 퍼즐 조각이자, C++ 코어 해커들의 궁극적인 엔드게임(End-game)인 **‘제로-카피(Zero-copy) 발행/구독(Publish/Subscribe) 시스템’**에 도달했다.
우리가 19단원 내내 가장 고상하고 안전한 무결점 함수라며 떠받들어 왔던 orb_publish()와 orb_copy()의 내부에는, 사실 매우 불편하고 무거운 물리학적 족쇄가 채워져 있다. 바로 **‘메모리 딥 카피(Deep Copy)’**의 의무이다.
일반적인 퍼블리셔 환경에서, 개발자가 만든 로컬 변수(구조체)의 데이터는 orb_publish를 만나는 순간 uORB 커널의 링 버퍼(Shared Memory)로 통째로 **복사(Copy)**된다. 그리고 구독자가 orb_copy를 치는 순간, 커널의 링 버퍼에 있던 데이터가 다시 한번 구독자의 로컬 변수 메모리로 **복사(Copy)**된다.
즉, 데이터 하나를 전달하기 위해 RAM 영역에서 두 번의 무거운 바이트 복사 오버헤드가 발생하는 것이다.
GPS나 가속도같이 고작 40바이트짜리 메시지라면 이 정도 복사는 1000Hz로 돌려도 CPU가 웃고 넘길 수준이다.
1. 빅데이터(Big Data)의 병목: 비전 센서와 딥 카피의 몰락
하지만 당신이 픽스호크 보드에 고해상도 옵티컬 플로우(Optical Flow) 카메라나 레이저 스캐너(LiDAR)를 장착하고, 한 틱에만 무려 **2MB(메가바이트)**가량의 포인트 클라우드 배열 구조체를 uORB로 퍼블리싱하려고 시도한다면 어떻게 될까?
2MB의 데이터를 30Hz로 딥 카피 퍼블리싱하고, 그걸 여러 구독자가 다시 딥 카피로 찍어 단다면, 오직 이 ‘메모리 복사 연산(memcpy)’ 하나만으로 전체 시스템 CPU 대역폭의 80%가 증발해 버릴 것이다.
아무리 빠른 RTOS 멀티스레드 환경이라도, 이 거대한 매트릭스를 복사하는 물리적인 I/O 딜레이 타임은 절대 이겨낼 수 없다.
2. uORB의 궁극기: 포인터 비행 (uORB Zero-copy Architecture)
이 절망적인 빅 데이터 병목을 타개하기 위해, 최신 PX4의 uORB 커널은 **데이터 복사를 완전히 폐기하고 구조체의 메모리 주소(Pointer) 껍데기만 안전하게 돌려쓰는 ‘Zero-copy API’**를 은밀하게 지원한다.
[Zero-copy 통신의 무자비한 논리]
- 퍼블리셔는 로컬 변수를 만들지 않는다. 대신
orb_advert_t방(Topic)을 만들 때 커널에게 “내가 직접 덮어쓸 메모리 방의 주소(Pointer)를 내놔라“라고 요구하여 미리 할당된 공유 버퍼 메모리의 배타적 쓰기 권한을 강제로 빼앗아 온다. (이것이 바로orb_claim()개념의 파생이다) - 이후 퍼블리셔는 그 공유 메모리 주소 위에 바로 LiDAR 데이터를 쏜다.
- 그리고 커널에게 **“나 다 썼으니 주소표만 갱신해라”**라고 툭 던진다(
orb_publish_auto혹은 객체 지향uORB::Publication::publish()참조 전달 통과). 이 과정에서 단 1바이트의 메모리 복사도 일어나지 않는다! - 반대편의 구독자 역시
copy()를 호출하여 자기 쪽으로 데이터를 끌어오지 않는다. 대신uORB::SubscriptionData와 같은 최신 C++ 래퍼(Wrapper) 템플릿을 사용하여, 커널 링 버퍼 원본 위에 얌전히 떠 있는 데이터의 **읽기 전용 포인터(Const Pointer / Reference)**를 락(Lock) 없이 그대로 열람한다.
[Zero-copy 구독의 예시적 파편]
// 매번 복사(copy)하는 구형 패턴
vehicle_odometry_s odom_data;
_odom_sub.copy(&odom_data); // 여기서 200Byte 통째 복사 발생!
// 새로운 C++ 래퍼(uORB::SubscriptionData)를 활용한 최신 Zero-copy 열람 패턴
uORB::SubscriptionData<vehicle_odometry_s> _odom_sub{ORB_ID(vehicle_odometry)};
_odom_sub.update(); // 틱만 갱신
const vehicle_odometry_s &odom_ref = _odom_sub.get(); // 주소(Reference)만 공짜로 가져온다!
// 원본 공유 메모리를 복사 없이 직접 리딩! 시간 비용 0초달!
double speed = (double)odom_ref.velocity[0];
이 제로-카피 기법을 코드에 휘두를 수 있게 된다는 것은, 당신이 C++의 포인터 생명 주기와 읽기/쓰기 락(Race Condition)의 메커니즘을 OS 커널 차원에서 완벽히 꿰뚫고 통제할 수 있는 **마스터(Master)**의 경지에 올랐음을 뜻한다.
당신이 만약 카메라, 라이다, 혹은 다중 컴퓨터 비전 데이터를 처리하는 뚱뚱한 uORB 노드를 개발하고 있다면, 결코 전통적인 딥 카피의 안정성에 만족하지 마라. 전방 선언으로 헤더를 다이어트 시킨 뒤, 거대한 Payload 구조체는 오직 이 제로 카피 인터페이스에 태워 스레드 간에 던지듯 토스(Toss)하는 것.
이것이 바로 자원이 극도로 제한된 마이크로 발사체(MCU) 환경에서 소프트웨어의 극한 효율을 뽑아내는, PX4 10년 차 장인들의 가장 파괴적이고 아름다운 종착역(Best Practice)인 것이다.