21.4.2.2.1. `start()`: `instantiate()` 호출 전후의 메모리 힙(Heap) 검사 및 동적 할당 실패에 대한 Fallback 시퀀스

21.4.2.2.1. start(): instantiate() 호출 전후의 메모리 힙(Heap) 검사 및 동적 할당 실패에 대한 Fallback 시퀀스

PX4 커스텀 모듈을 만들 필드 개발자라면, ModuleBase를 상속받은 뒤 무조건 첫 번째로 오버라이드(Override)해야 하는 함수가 바로 start()이다.

// CustomApp.cpp
int CustomApp::start()
{
    // ... 메모리 할당 및 스레드 런처 ...
}

이 함수 안에서 벌어지는 일의 핵심은 단 하나, 바로 무형의 클래스 설계도를 바탕으로 램(RAM)의 힙(Heap) 영역에 실제 데이터를 담을 수 있는 유일한 공간(Instance)을 동적으로 뚫어내는 것이다.

1. instantiate(): 객체 생성과 포인터 바인딩

PX4 모듈 시스템에서는 내가 짠 클래스를 내가 직접 new CustomApp() 으로 호출하지 않는다. 대신 ModuleBase가 제공하는 템플릿 메타 함수인 **instantiate(argc, argv)**를 경유하여 객체를 생성하는 것이 표준이다.

instantiate() 함수 내부에는 C++의 기본 new 연산자가 숨어있으며, 이 연산자가 성공하면 부모 클래스의 비밀스러운 전역 포인터(싱글톤 포인터)에 방금 만든 따끈따끈한 객체의 주소값을 알아서 결속(Binding)시켜 준다.

따라서 우리의 start() 함수는 아래와 같은 표준 패턴의 뼈대를 가져야 한다.

int CustomApp::start()
{
    // 1. 메모리 생성 시도
    bool instantiate_success = instantiate(0, nullptr);

    // 2. 동적 할당 실패 시의 뼈아픈 Fallback 로직
    if (!instantiate_success) {
        PX4_ERR("alloc failed");
        return -1; // 시스템 크래시를 막고 우아하게 포기
    }

    // 3. 할당이 끝난 후 해야 할 부가 작업 (센서 오픈 등)
    // ...

    return 0; // 정상 시작
}

2. 비참한 메모리 고갈과 Fallback (후퇴) 시퀀스

위 코드의 두 번째 단계인 if (!instantiate_success) 는 우주항공 코딩에서 생명줄과도 같다.

픽스호크 보드는 데스크톱 리눅스처럼 RAM 공간이 하드디스크 드라이브까지 가상으로 늘어나는 스왑(Swap) 메모리 따위는 존재하지 않는다. 2MB짜리 물리적인 플래시 메모리가 꽉 차는 순간, 그 뒤에 줄 서 있던 new 연산자들은 죄다 쓸쓸하게 nullptr (Null Pointer)를 반환하며 터져나간다.

만약 이 컴포넌트가 중요도가 떨어지는 보조 로깅(Logging) 모듈이었고, 마침 메인 비행을 위해 수백 킬로바이트의 큐(Queue) 메모리를 확보해야 하는 메인 EKF 제어기가 동시에 켜지는 상황이었다고 가정해 보자.

비핵심 모듈의 start() 안에서 저 if 문 강제 검사가 없었다면?
비핵심 모듈은 억지로 메모리를 점유하려다 시스템 전체에 Hard Fault(메모리 참조 에러)를 유발하고 추락의 방아쇠를 당겼을 것이다. 하지만 이처럼 **정확히 return -1; 로 실패를 인정하고 포기(Fallback)**하면, NSH 콘솔에는 붉은 글씨로 “alloc failed” 문자 한 번 띄워주고 끝날 뿐 어설프게 객체가 만들어져 돌아다니는 좀비 현상을 완벽하게 차단할 수 있다.

이제 객체를 예쁘게 살려냈으니, 사용자가 터미널에서 ’그만해라’라고 명령했을 때 이 객체가 자살하지 않고 우아하고 깨끗하게 힙 메모리를 반환한 뒤 사라지는 매너 있는 퇴장 방식(stop())을 다음 단원 21.4.2.2.2 에서 뜯어보자.