27.2.1.1. 코어 필터링 알고리즘 분리 및 C++ 표준 라이브러리 사용 최소화 원칙
PX4의 ECL(Estimation and Control Library) 아키텍처를 소스 코드 레벨에서 가장 특징짓는 요소는 바로 “극도로 통제된 C++ 언어의 사용” 이다. ECL은 최신 C++ 문법을 허용하지만, C++이 자랑하는 강력한 표준 템플릿 라이브러리(STL: Standard Template Library)의 사용을 엄격하게 금지하거나 최소화하는 독특한 코딩 컨벤션(Coding Convention)을 강제한다.
본 절에서는 코어 필터링 알고리즘이 분리된 환경에서, 왜 ECL 개발진이 std::vector나 std::map 같은 편리한 도구들을 배척하고 원시적인 형태의 정적(Static) 프로그래밍 기법을 고수하는지 그 공학적 당위성을 분석한다.
1. 하드 리얼타임(Hard Real-Time)과 메모리 결정론(Determinism)
드론의 상태를 추정하는 EKF(Extended Kalman Filter) 연산 루프는 보통 250Hz에서 400Hz 사이의 주기로 쉴 새 없이 돌아간다. 이는 0.0025초마다 24차원의 행렬 미적분 연산이 반드시 한 번씩 ‘칼같이’ 완료되어야 함을 의미한다.
만약 이 엄격한 마감 시간(Deadline)을 한 번이라도 놓치는 지터(Jitter)가 발생하면, 적분기에는 오차가 누적되고 기체는 물리적으로 요동치게 된다. 이러한 하드 리얼타임(Hard Real-Time) 시스템에서 가장 치명적인 적은 바로 동적 메모리 할당(Dynamic Memory Allocation) 이다.
1.1 std::vector 와 new/malloc의 치명적 설계 결함
C++ STL의 핵심인 std::vector나 동적 할당 연산자인 new는 내부적으로 운영체제의 메모리 힙(Heap) 공간을 뒤져서 자유로운 빈 공간을 할당받는다. 이 과정의 치명적인 문제점은 다음과 같다.
- 비결정적 실행 시간(Non-deterministic Execution Time): 메모리 관리자가 파편화(Fragmentation)된 힙 메모리 공간을 탐색하는 데 걸리는 시간은 매번 다르다. 어느 타임스텝에서는 1\mu s가 걸리지만, 운이 나쁘면 100\mu s가 걸릴 수도 있다. 이는 EKF 루프 타이밍의 붕괴를 초래한다.
- 메모리 파편화(Memory Fragmentation): 드론이 1시간 동안 비행하며 수백만 번의 동적 메모리를 할당하고 해제(
delete)하다 보면, 마이크로컨트롤러(MCU)의 한정된 256KB 남짓한 내부 RAM은 마치 구멍 뚫린 치즈처럼 파편화되어 최종적으로 ‘Out of Memory(OOM)’ 커널 패닉을 일으키고 기체를 추락시킨다.
2. ECL의 C++ 설계 원칙: 정적(Static) 할당 기반 아키텍처
이러한 재플랫폼(Re-platforming) 및 리얼타임 안정성 이슈를 피하기 위해, ECL 프로젝트는 펌웨어 독립적인 필터링 코어를 설계할 때 다음과 같은 엄격한 코드 룰을 적용했다.
2.1 예외 처리(try-catch) 및 RTTI 금지
C++의 예외 구조(Exception)와 런타임 타입 정보(RTTI: Run-Time Type Information)는 컴파일된 바이너리의 크기를 비대하게 만들고 실행 시간 예측을 불가능하게 하므로 ECL 코드 베이스에서는 -fno-exceptions, -fno-rtti 컴파일 플래그를 통해 완전히 차단된다. 오류가 발생하면 예외를 던지는 대신, 불리언(Boolean) 반환값이나 상태 플래그를 통해 명시적으로 오류를 상위로 전파한다.
2.2 고정 크기 정적 배열(Static Array)의 강제
ECL 코어 내에서 센서 버퍼나 행렬 변수를 선언할 때는 힙(Heap)을 사용하는 동적 배열을 절대 쓰지 않는다. 대신 빌드 타임에 크기가 결정되는 전역(Global), 스태틱(Static), 또는 심스페이스(Stack) 변수 형태의 고정 크기 원시 배열(Raw Array)만을 허용한다.
// [안 좋은 예] 절대 허용되지 않는 코드 (C++ STL 사용)
std::vector<gpsSample> _gps_buffer;
_gps_buffer.push_back(new_data); // 내부적으로 malloc() 발생 가능성
// [올바른 예] PX4 ECL에서 사용하는 정적 링 버퍼 구현 방식
static constexpr uint8_t OBS_BUFFER_SIZE = 12;
gpsSample _gps_buffer[OBS_BUFFER_SIZE]; // Stack 또는 BSS 섹션에 고정 할당
uint8_t _gps_buffer_head = 0;
// 포인터 연산을 통한 인덱스 관리로 동적 할당 원천 차단
2.3 Eigen 라이브러리의 배제와 초경량 matrix 라이브러리 도입
로봇 공학이나 딥러닝 분야에서 선형 대수학 C++ 라이브러리로 가장 널리 쓰이는 것은 단연 Eigen 라이브러리다. 하지만 PX4 ECL은 이 훌륭한 라이브러리마저 도입을 거부했다. Eigen의 템플릿 메타 프로그래밍 스택이 임베디드 코어에 올리기에는 지나치게 방대하고, 자칫 잘못 선언하면 런타임 힙 동적 할당을 유발할 수 있기 때문이다.
대신, PX4 개발팀은 오직 항공우주용 임베디드 EKF 연산에 꼭 필요한 기능(역행렬, 전치행렬, 쿼터니언 변환 등)만을 아주 가볍게 추려내어, 완전히 템플릿 헤더 기반으로 정적 메모리 위에서만 돌아가는 독자적인 matrix 라이브러리(src/lib/matrix)를 직접 개발하여 ECL의 뼈대로 삼았다.
3. 요약: 가장 원시적인 것이 가장 안전하다
결과적으로 PX4 ECL 라이브러리의 C++ 코드를 들여다보면, 최신의 세련된 std::shared_ptr 스마트 포인터나 std::map 컨테이너는 찾아볼 수 없다. 그 자리에는 C 언어 시절의 투박한 고정 배열과, 인덱스 바운더리 검사를 직접 수행하는 딱딱한 링 버퍼(Ring Buffer) 구현체들이 자리 잡고 있다.
하지만 이러한 ‘수학적 고집’ 과 ‘메모리 제어권의 완전한 장악’ 덕분에, PX4 ECL은 불과 수백 킬로바이트(KB) 남짓한 싸구려 코어텍스(Cortex-M 시리즈) 마이크로컨트롤러 위에서도 단 1 마이크로초(us)의 지체나 단 1 바이트의 메모리 누수 없이, 평생을 비행해도 결코 죽지 않는 완벽한 상태 추정 오라클(Oracle)로 동작할 수 있는 것이다.