28.2.3. 메인 제어 루프(Run()) 실행 파이프라인
앞선 아키텍처 구조 분석을 통해, PX4의 FlightModeManager (이하 FMM) 데몬이 NuttX 운영체제의 nav_and_controllers 워크 큐(Work Queue) 스레드 풀에 기생하여 이벤트 주도로 살아간다는 사실, 그리고 램 공간 위에서 C++ 다형성 다이내믹 포인터를 통해 구체적인 수학 객체들(FlightTask)을 팩토리 패턴으로 찍어내고 부수며 스위칭한다는 메타적 사실들을 확인했다.
이제 그 모든 거시적 아키텍처 철학이 초당 400Hz의 굉음을 내며 실제로 격돌하여 구체적인 물리적 궤적 벡터로 갈려 나오는 좁디좁은 단일 병목 관문, 바로 FMM 데몬의 심장부인 Run() 메인 제어 루프의 실행 파이프라인(Execution Pipeline) 내부를 코드 수행 순서대로 정밀 타격하여 분석해 본다.
1. 0단계: 기상(Wake-up) 및 인터럽트 확인 (OS \rightarrow 데몬)
FMM 데몬은 스스로 무한 while 루프를 돌며 CPU를 소모하지 않는다. 하위 계층의 EKF2(상태 추정기)가 vehicle_local_position 토픽을 새롭게 uORB 버스에 발행(Publish)하는 그 순간, 워크 큐 스케줄러가 잠자던 FMM의 머리통을 때려 깨우며 Run() 함수가 단발성으로 1회 호출된다.
Run()함수 내부 제일 첫 줄 진입 시점에서는 가장 먼저 자신이 왜 깨어났는지 비몽사몽간에 확인하는 절차를 거친다. 외부에서 스케줄을 명시적으로 취소(ScheduleClear())했거나 시스템 종료 킬 스위치가 떨어졌는지 검사하고, 만약 종료 타겟이라면 메모리 반환 루틴으로 즉각 빠져버린다.- 정상적인 주기적 깨어남일 경우, 자신의 손목시계 역할을 하는
hrt_absolute_time()하드웨어 실시간(Real-time) 타이머 값을 읽어내, 직전틱(Tick) 스위칭 루프 대비 정확히 몇 마이크로초(\mu s)의 <Delta Time (dt)>이 흘렀는지 물리적 시간 적분 상수를 가장 먼저 엄격하게 계산해 확보한다.
2. 1단계: 주변 상황 파싱 및 글로벌 상태 갱신 (센서 \rightarrow FMM 내부 버퍼)
본격적인 3D 궤적 산출 수학 계산에 들어가기 앞서, FMM 뼈대 코어는 영악하게도 외부 세상의 최신 센싱 데이터를 단일 구조체 팩으로 싹쓸이하여 수거해 온다.
- uORB 폴링(Polling) 업데이트:
FMM 클래스의 프라이빗 멤버로 달려있던 수많은 uORB 구독자(Subscriber) 포인터들(_vehicle_status_sub,_manual_control_setpoint_sub등)의update()API를 연쇄적으로 두들겨 패서 캐시 버퍼를 비우고 최신 램 데이터로 갱신한다. - 종합 상태 묶음 객체 동기화:
이전 챕터 DI(의존성 주입) 로직에서 다룬 것처럼, 분산 채집되어 수거된 다양한 토픽 센서 조각 데이터들을vehicle_local_position_s등의 단일하고 거대한 상태 관리 구조체(State Container)안에 정렬하여 이쁘게 최신화 복구해 둔다. 이제 FMM의 하위 수하인FlightTask자식 객체들은 외부 세계의 변화를 1도 신경 쓰지 않고, 오직 이 완성된 요리책(Container) 참조 포인터만 던져 받아 읽어 계산하면 되는 상태가 완벽히 셋업 되었다.
3. 2단계: 핵심 의도 분석 및 아키텍처 다이내믹 스위칭 (Commander \rightarrow Task Factory)
데이터 수거가 끝나면, 즉시 상위 Commander 가 내려보내는 사용자의 비행 의도(vehicle_status.msg 내 nav_state 열거형 고유 번호)가 과거 틱(Tick)과 비교하여 변경되었는지 에지 검출(Edge Detection)을 찰나의 조건문으로 수행한다.
- 만약 조종사가 스위치를 쳐서 모드가 변경된 엣지(Edge) 순간이라면,
switchTask()트랜잭션 함수가 발동되어 거대한 기어박스가 돌아간다. 기존의 C++ 비행 수학 객체는 무자비하게 힙에서delete살해당하고 파괴되며, 새 모드 번호에 맞는 신규FlightTask자식 객체가new오퍼레이터로 즉각 복제되어 올라와 부모 다형성 포인터 끈인_current_task에 핫스와핑 연결 장착(Mount)된다. - 모드가 변경된 순간이 아니라면(99.9% 런타임), 이 막중한 메모리 교체 스위칭 블록은 0.00ms의 오버헤드로 시크하게 패스(Bypass)된다.
4. 3단계: 다형성 궤적 연산 위임 (FMM \rightarrow FlightTask)
아키텍처상 이 Run() 무대 위에서 가장 많은 CPU 연산 열이 발생하고 수학적 매트릭스가 튈 법한 단계이지만, 정작 FMM 본체 코어 파일 내부의 C++ 코드는 이 지점에서 허탈할 정도로 단 한 줄 코드로 종결된다.
// 다형성(Polymorphism)의 마법을 통한 연산 책임 전가
_current_task->update();
바로 전 단계에서 핫스와핑으로 갈아 끼워진, 혹은 원래 달고 돌아가던 구체적 자식 객체의 가상 함수(update()) 포인터로 냅다 점프(Jump) 콜을 때려버린다.
만약 현재 _current_task 끈에 매달린 객체가 자동 복귀(FlightTaskAutoRtl) 객체라면, 저 한 줄의 코드는 안으로 비집고 들어가 GPS 좌표점 간의 3차원 직선 복귀 경로를 미적분 연산하는 수백 줄의 매트릭스 알고리즘을 수행하고 나오게 된다. FMM 브로커 메인 관리자는 자식 객체가 땀 흘려 연산을 마칠 때까지 그저 블로킹(Blocking)된 채 차갑게 대기한다.
5. 4단계: 결과 퍼블리싱 및 스레드 수면 (FMM \rightarrow uORB 및 OS 스케줄러)
자식 객체가 땀 흘려 궤적 수식(update()) 연산을 마치고 성공적으로 리턴되면, FMM 본체는 다시 주도권을 찾아온다.
- 최종 궤적 배달: 자식 객체가 자신의 내부 메모리 버퍼에 저장해 놓은 3차원 위치 목표(Target Position), 속도 목표(Velocity Setpoint), 가속도 피드포워딩(Acceleration Feed-forward) 결과 집합체 데이터를 끄집어 뽑아내, PX4 시스템의 공식 통용 화폐인
trajectory_setpoint.msg토픽 구조체 그릇에 담는다. 그리고 하단 자세 제어기들이 기다리고 있는 uORB 우체통으로 이를 힘껏 브로드캐스팅 퍼블리시(Publish)해 발송한다. - 유가족 타스크 예약과 굿나잇 수면: 본인의 400Hz 단일 틱(Tick) 임무를 무사 완수한 FMM 데몬은, 만약 uORB 이벤트가 들어오지 않더라도 혹시 모를 에러 다운을 방지하기 위해 최소 50ms 후에는 날 강제로 깨워달라고 커널 스케줄러 시계 알람을 맞추는
ScheduleDelayed(50000)유언장 함수를 유유히 호출한다. - 그리고
return문을 통해Run()함수 스코프 밖으로 스레드 실행 제어권을 내던져 버림과 동시에 즉각 수면 상태로 기절(Suspend)하며, 하단 워크 큐의 다음 타자인 파이프라인 형제(mc_pos_control) 데몬에게 CPU 연산 마이크를 폭력적으로 넘겨주며 1 사이클이 종료된다.