28.2.2.1.1. 의존성 주입(Dependency Injection) 및 내부 상태 변수(State Variables) 구조
모던 소프트웨어 객체 지향 플러그인 설계(OOP)에서 모듈 간의 더러운 하드코딩 결합도(Coupling)를 가장 우아하게 낮추고 테스트 용이성(Testability)과 코드 격리성을 극대화하는 핵심 기법이 바로 의존성 주입(Dependency Injection, DI) 패턴이다.
PX4의 FlightModeManager (이하 FMM) 데몬 본체는, 램 메모리 위에 들락날락하며 다이내믹 핫스와핑으로 생성되고 소멸을 반복하는 수십 개의 비행 궤적 산출 자식 클래스(FlightTask 파생 객체)들을 스케줄링 관장하는 과정에서, 이 DI 디자인 패턴을 교과서적으로 교활하게 활용하여 무거운 uORB OS 미들웨어 통신 자원 낭비를 극한의 0 픽셀로 억제해 낸다.
1. 개별 uORB 데이터 구독의 중앙 집중화와 무자비한 DI 주입 메커니즘
현대 자율 비행 드론이 공중 3D 공간을 가로지르는 목표 위치 궤적 매트릭스를 수학적으로 계산해 내기 위해서는, 필수 불가결하게 기체의 현재 3차원 지역 좌표(vehicle_local_position), 회전 자세 쿼터니언(vehicle_attitude), 그리고 인간 조종사가 꺾고 있는 RC 스틱의 찰나의 아날로그 값(manual_control_setpoint) 등 수많은 종류의 센서 도메인 외부 파이프 데이터가 입력 피연산자로 요구된다.
- 독립 구독의 아키텍처적 메모리 지옥 (The Anti-pattern):
만약 FMM 소스 트리 밑단에 존재하는 10개가 넘는 다양한 파생 커스텀FlightTask(비행 모드) 객체들이, 각자 무질서하게 자신의 CPP 클래스 생성자 내부에서 위 토픽들을 독립적으로 uORBSubscription노드 핸들을 10번 열어 개별 구독(Subscribe)한다면 어떤 일이 벌어질까? EKF2 필터가 발행한 동일한 위치 센서 데이터 1바이트를 복사하기 위해, 수십 배 단위의 스레드 버퍼가 스택에 복잡하게 낭비되고 얽혀 커널의 인터럽트 이벤트 브로드캐스팅(Broadcasting) 오버헤드 사이클이 400Hz 주파수 위에서 기하급수적으로 부하 폭발하게 된다. - FMM 뼈대 브로커의 중앙 집중식 데이터 통제와 DI 아키텍처:
PX4 코어 아키텍트 설계자들은 이 끔찍한 병목 낭비를 원천 봉쇄하기 위해, 모든 무거운 시스템 데이터 구독(Subscription) 핸들 권한을 오직 최상단 FMM 부모 브로커 본체(FlightModeManager.cpp) 단 하나에만 독재적으로 중앙 집중시켰다.
FMM 부모 본체 구조체가 400Hz 타임 알람Run()루프를 돌 때마다 커널 버스를 뒤져 모든 최신 uORB 센서 토픽 메시지 데이터를 싹쓸이 파싱한 다음, 이를FlightTask추상 클래스가 요구하는 거대하고 단일한 C++ 구조체 메모리 묶음 블록(State Variables Struct)으로 이쁘게 압축 취합하여 갱신한다.
그리고 가장 압권인 지점은,switchTask()팩토리 매크로가 사용자의 조종기 스위칭에 따라 새로운 자식 비행 모드 수학 객체 인스턴스를 메모리 힙에 동적 생성(new FlightTaskX)하는 찰나의 순간에, 바로 이 취합 업데이트된 거대 통합 센서 **상태 포인터 버퍼 주소(Reference Memory Pointer)를 자식 객체의 생성자(Constructor) 인자로 단칼에 무자비하게 밀어 넣어 원시 주입(Dependency Injection)**해 버린다.
이 놀라운 아키텍처 덕분에, 말단 비행 수학 제어 연산 객체(Custom Task) 입장에서는 자기 스스로 무거운 외부 uORB OS 커널 미들웨어 세상과 귀찮게 인터럽트 통신 소켓을 뚫어 소통할 필요가 전혀 없어진다. 그저 지휘자 부모(FMM)가 매 틱(Tick)마다 입에 자상하게 떠먹여 주는 주입 완료된 1계층 상태 변수 포인터 주소만 낼름 받아 읽어다가 순수한 궤적 행렬 수학 공식 연산에만 100% 퓨어 CPU 뇌용량을 집중하면 되는 완벽하게 격리된 샌드박스(Sandbox) C++ 팩토리가 완성되는 것이다.
2. 내부 메모리 상태 변수(State Variables) 쌍의 엣지 디텍션(Edge Detection) 트리거
FMM 메인 C++ 클래스 헤더를 면밀히 분석해 보면, 단순히 현재 상태 포인터 값 하나만을 컴파일 러닝타임에 쥐고 있는 것이 아니라, 과거 틱(Tick)의 히스토리 흔적을 끈질기게 두 개의 포인터 쌍(Pair)으로 물고 늘어지는 듀얼 내부 상태 변수들을 쉽게 발굴해 낼 수 있다.
uint8_t _nav_state{vehicle_status_s::NAVIGATION_STATE_MAX};
uint8_t _nav_state_previous{vehicle_status_s::NAVIGATION_STATE_MAX};
이 코드는 단순한 무지성 정보 저장 버퍼를 넘어선, 타임 크리티컬 RTOS 임베디드 스케줄러 환경에서의 필수적인 아키텍처적 트릭 기법이다.
초당 400Hz(2.5ms 주기)로 미친 듯이 미세하게 순환하는 연속적인 무한 루프 메커니즘 시간축 속에서, 조종사가 조종기 토글 스위치를 ‘딸깍’ 하고 물리적으로 쳐서 1번 모드에서 2번 모드로 변경하는 그 단 한 번의 단문(Single Tick)의 순간 절벽 엣지(Trigger Edge Transition) 찰나를 잡아내 트리거하기 위한 가장 싸고 비용 지불 없는 C++ 아날로그적 장치다.
FMM의 Run() 베이스 함수는 무거운 궤적 래핑 루프를 돌 때마다 오직 컴파일러 레벨의 가장 싼 1비트 비교 연산인 if (_nav_state != _nav_state_previous) 라는 가벼운 어셈블리어 조건문 단 하나만을 CPU에 태워 비교한다. 이 두 변수 포인터 값이 물리적으로 틀어지는 오직 그 0.1밀리초 찰나의 순간의 틱에서만 무거운 OOM 위험 트랜잭션 수술인 switchTask() 동적 메모리 핫스와핑 로직을 무겁게 단발성으로 터뜨리고, 나머지 99.9% 평시 순항 시간 동안은 궤적 산출만 가볍게 수행하여 전체 OS 프로세서의 사이클 타임을 극한으로 쥐어짜 아껴내고 있다.