21.1.2.1.2. 선점형(Preemptive) 컨텍스트 스위칭으로 인한 분기 예측 실패 및 CPU 캐시 미스(Cache Miss) 증가
앞서 다루었듯 task_create() 방식의 독립 태스크 아키텍처는 공간적 측면에서 ’SRAM 파편화’라는 꼬리표를 달고 다녔다. 하지만 하드 리얼타임(Hard Real-time) 비행 제어기 관점에서는 공간의 낭비보다 더 뼈아픈 것이 바로 시간의 낭비이다.
NuttX OS의 선점형 스케줄러(Preemptive Scheduler)는 각 태스크들에게 공평하게 CPU 마이크로초(us)를 나누어주기 위해, 하루에도 수십만 번씩 실행 중이던 태스크의 모가지를 치고 다른 태스크를 강제로 실행시키는 **문맥 교환(Context Switching)**을 단행한다. 이 짧은 교차로에서 발생하는 실리콘 칩 단의 연산 지연은 상상을 초월한다.
1. 문맥 교환(Context Switching)의 잔혹한 순서도
픽스호크의 Cortex-M 프로세서 위에서 태스크 A(예: 배터리 모니터)가 도는 와중에, 더 높은 우선순위를 가진 태스크 B(예: EKF2 자세 추정)가 깨어나면 CPU 내부에서는 다음과 같은 폭력적인 대청소가 일어난다.
- 레지스터 대피 (Register Pushing): CPU는 즉각 실행을 멈추고 제어 유닛(ALU)에 들어있던 상태 레지스터(R0~R15, PC, LR, xPSR 등)와 FPU(부동소수점) 레지스터 수십 개를 태스크 A의 램(SRAM) 스택 영역으로 꾸역꾸역 백업(Push)한다.
- 스택 포인터 교체 (SP Swap): OS는 스택 포인터(SP) 위치를 태스크 A의 메모리 번지에서 태스크 B의 메모리 번지로 홱 틀어버린다.
- 레지스터 복원 (Register Popping): 태스크 B가 지난번에 멈췄던 지점의 레지스터 값들을 다시 램에서 긁어와(Pop) CPU의 심장부로 쑤셔 넣고, 프로그램 카운터(PC)를 재가동한다.
이 피곤한 메모리 복사 작업만으로도 이미 수십 클록 틱(Clock Tick)이 허비된다. 하지만 진짜 비극은 메모리 복사 그 너머에 있는 **파이프라인 리셋(Pipeline Reset)**에 있다.
2. CPU 캐시 미스(Cache Miss)와 분기 예측(Branch Prediction) 붕괴
최신 H7 급 마이크로컨트롤러는 명령어를 단일 사이클로 처리하기 위해, 다음에 실행될 코퍼드(Code Data)를 플래시(Flash) 메모리에서 미리 퍼와 고속의 **명령어 캐시(I-Cache)**와 **데이터 캐시(D-Cache)**에 꽉꽉 채워둔다(Prefetch). 또한, if-else 문을 만났을 때 어느 쪽으로 튈지를 똑똑하게 찍어보는 **분기 예측기(Branch Predictor)**가 맹렬히 돌아가고 있다.
그런데 OS 스케줄러가 문맥 교환을 단행하는 순간, 이 모든 고속도로는 잿더미가 된다.
- 캐시 플러시(Cache Flush): CPU는 갑자기 완전히 다른 메모리 번지에 위치한 태스크 B의 코드를 실행해야 하므로, 기존에 정성껏 모아두었던 태스크 A의 캐시 메모리 내용을 전부 쓰레기통에 버린다(Invalidate/Flush).
- 콜드 스타트(Cold Start): 태스크 B는 텅 빈 캐시를 마주하며, 느려 터진 플래시 메모리에서 다시 한 땀 한 땀 코드를 퍼올려야 하는 캐시 미스(Cache Miss)의 극심한 지연을 겪는다.
- 분기 예측기 초기화: 분기 예측기의 히스토리 버퍼가 초기화되어, 루프 문(Loop)을 돌 때마다 예측 실패로 인한 파이프라인 스톨(Pipeline Stall)이 연쇄적으로 발생한다.
3. 시스템 주파수(Hz) 향상의 영원한 병목
초창기 픽스호크가 250Hz로 비행할 때는 1초에 250번의 모터 업데이트만 하면 되었으므로, 이 문맥 교환 패널티(수십 마이크로초)를 그럭저럭 견딜 만했다.
하지만 레이싱 드론이나 정밀 매핑 드론이 등장하면서 비행 제어 루프를 1000Hz, 나아가 2000Hz로 끌어올리려 하자 문제가 터졌다. 1000Hz(1ms 주기) 환경에서는 문맥 교환 패널티가 전체 제어 시간의 10% 이상을 잡아먹는 ’하드웨어 병목’으로 군림하게 된 것이다.
이제 PX4 아키텍트들은 “어떻게 하면 문맥 교환 없이, 수십 개의 모듈 코드를 CPU 캐시를 유지한 채로 연달아 렌더링할 수 있을까?“라는 질문에 도달했다. 그 위대한 해답이 바로 다음 21.1.2.2 단원에서 다룰 협력적(Cooperative) Work Queue 아키텍처이다.