21.7.1.1.1. 동적 할당을 회피하고 모듈 스택 내에 메시지 버퍼를 미리 확보하여 페이지 폴트(Page Fault)를 방지하는 구조

21.7.1.1.1. 동적 할당을 회피하고 모듈 스택 내에 메시지 버퍼를 미리 확보하여 페이지 폴트(Page Fault)를 방지하는 구조

C++에서 무언가 큰 덩어리의 객체 버퍼(Buffer)를 내부에 지니고 있는 클래스를 보면, 전통적인 데스크톱 프로그래머들은 본능적으로 new 연산자나 malloc()을 떠올린다.
“아, SubscriptionData<sensor_gyro_s> 객체가 생성될 때 저 내부에 new sensor_gyro_s를 호출해서 동적으로 힙(Heap) 메모리를 따오겠구나!”

만약 PX4 코어팀이 그런 끔찍한 방식으로 템플릿을 설계했다면, 픽스호크 보드는 비행을 시작하자마자 메모리 파편화(Fragmentation)로 산산조각 났을 것이다.

1. 동적 할당(malloc)의 치명적 결함: 비결정성(Non-determinism)

수수께끼는 바로 **‘실시간 운영체제(RTOS)의 생명은 예측 가능성(Determinism)’**이라는 대원칙에서 출발한다.

malloc()이나 new를 호출하여 동적 메모리를 요구하는 행위의 처리 시간은 O(1)이 아니다.
운영체제의 메모리 관리자는 메모리 파편들 속에서 딱 맞는 빈 공간을 찾기 위해 링크드 리스트를 뒤지게 되며, 운이 나쁘면 빈 공간을 병합(Defraction) 하느라 수 밀리초(ms) 넘는 극악의 지연(Latency)을 발생시킨다.

더욱 끔찍한 것은 물리 메모리가 꽉 차버렸을 때 발생하는 페이지 폴트(Page Fault) 혹은 OOM (Out Of Memory) 사태다.
비행 중에 갑자기 메모리가 모자라서 할당이 실패(nullptr 반환) 하거나, 가상 메모리 스왑이 일어나며 운영체제가 기절해 버리는 상황은 드론에게 곧 ’추락’을 의미한다.

따라서 픽스호크 시스템 내부에서는, 비행을 시작한 이후 런루프(Run-loop) 내에서 단 1바이트의 메모리라도 malloc()으로 끌어다 쓰는 행위를 법으로 엄격히 금지하고 있다.

2. SubscriptionData<T> 의 정적 버퍼(Static Buffer) 꼼수

그렇다면 SubscriptionData 템플릿은 그 거대한 센서 구조체를 어떻게 동적 할당 없이 품었을까?
템플릿의 소스 코드 원형을 까보면 아주 무식하고 원초적인 방법이 사용되었음을 알 수 있다.

// uORB::SubscriptionData 템플릿의 뼈대
template <typename T>
class SubscriptionData : public SubscriptionInterval {
private:
    // 어떠한 포인터(*)나 동적 힙(new) 할당도 존재하지 않는다!
    // 구조체 타입 T(예: sensor_gyro_s) 그 자체가 값(Value) 타입으로 통째로 박혀있다!
    T _data{}; 
};

클래스 내부에 T* _data; (포인터)가 아니라 **T _data{}; (값 타입 데이터)**를 그대로 우적우적 박아 넣었다.

이 한 줄이 만들어내는 아키텍처적 나비효과는 실로 어마어마하다.

  1. 메모리 팽창: 우리가 CustomApp 모듈 안에 SubscriptionData<sensor_gyro_s>를 5개 선언했다고 치자. 이 순간 CustomApp 객체 전체의 메모리 덩치(Size)는 자이로 구조체 5개 분량만큼 순식간에 뚱뚱하게 부풀어 오른다.
  2. 생성 시 일괄 확보: 이 거대하게 부풀어 오른 CustomApp 객체가 시스템 부팅 시점에 main() 함수에서 단 한 번 인스턴스화(Instantiate)될 때, 모듈 스레드의 메모리(Stack 또는 런타임 이전 1회성 Heap) 공간을 통째로 점유하며 **미리 덩어리째 확보(Pre-allocation)**된다.

3. 페이지 폴트 원천 차단: 예방주사 효과

이처럼 비행이 시작되기 전(부팅 초기화 단계)에 필요한 모든 메시지 버퍼를 클래스 인스턴스와 함께 한 덩어리로 잘라두면, 비행 도중(Run() 루프 안)에는 절대로 새로운 메모리 청크를 운영체제에게 조를 일이 사라진다.

데이터가 오면, 미리 잘라서 내 방 안에 가져다 둔 _data 변수 공간에 그저 값을 덮어쓰기만(Overwriting) 하면 끝이다.
이로써 우리는 최악의 가비지 컬렉션 지연이나, 비행 중 페이지 폴트(Page Fault)로 기체가 다운되는 참거를 컴파일-타임 템플릿 엔진의 메모리 합병 마술로 깨끗하게 사전에 회피해 낸 것이다.

이제 uORB의 구조체 덩어리를 어떻게 우아하게 복사해 오는지 완벽히 터득했다.
그렇다면 똑같은 ’GPS 데이터’나 ’자이로 데이터’가 한 대의 기체에서 두세 개씩 뿜어져 나올 때(중복 센서 시스템), 이 래퍼들은 어떻게 내가 원하는 센서 번호표(Instance)를 정확히 낚아채는지 다음 장(21.7.1.2)에서 현미경을 들이대 보자.