21.1.2.1. 독립 태스크(task_create) 방식의 구조적 한계점
과거 PX4 펌웨어 개발자들에게 px4_task_spawn_cmd() 또는 task_create() 함수는 마법의 지팡이와 같았다. 복잡한 로직을 고민할 필요 없이, 일단 이 지팡이를 휘둘러 나만의 독립된 스레드(태스크)를 하나 찍어내기만 하면 만사형통이었기 때문이다. 자신이 만든 무한 루프 블록 안에서는 usleep()으로 달콤한 잠에 빠져들어도 상관없었고, read() 함수에서 센서 응답이 올 때까지 하염없이 스레드를 블로킹(Blocking) 시켜놓아도 OS 커널이 알아서 다른 태스크를 실행해 주었다.
하지만 이러한 ’태스크 만능주의(Task driven architecture)’는 픽스호크(Pixhawk) 보드 위에서 돌아가는 컴패니언 센서와 제어 모듈의 개수가 10개를 넘어 50개, 100개로 폭발적으로 늘어가면서 거대한 구조적 재앙을 불러왔다.
1. 태스크 만능주의가 초래한 비극
이론적으로 독립 태스크 모델은 각 모듈의 프로그래밍 환경을 타 모듈로부터 완벽히 비동기적으로 분리(Decoupling)해 준다는 훌륭한 장점이 있다. 개발자 A가 만든 GPS 모듈이 응답 대기 지연(Latency)에 빠져 멈춰 있다 해도, 개발자 B가 만든 자세 제어 모듈 스레드는 OS 스케줄러에 의해 선점형(Preemptive)으로 CPU를 탈환하여 멈춤 없이 돌아갈 수 있기 때문이다.
그러나 하드웨어 리소스가 극도로 제한적인 마이크로컨트롤러(MCU) 세계에서 이 혜택은 너무나도 큰 비용을 청구했다. 코어 아키텍처 관점에서 task_create 방식이 가졌던 치명적인 한계점은 크게 두 가지로 요약된다.
- SRAM의 파편화와 만성적 메모리 기아(Starvation) 상태: 수십 개의 꼬마 태스크들이 제각기 방(Stack)을 하나씩 요구하면서 생기는 공간의 낭비.
- 문맥 교환(Context Switching)으로 인한 캐시 미스(Cache Miss)의 폭주: CPU가 A 스레드에서 B 스레드로 눈동자를 돌릴 때마다 발생하는 막대한 연산의 낭비.
현대 PX4가 왜 그토록 사용자 정의 앱(Custom App)에게 독립 태스크 스폰(task_spawn)을 자제하고 협력형 워크 큐(Work Queue)에 올라탈 것을 종용하는지 이해하기 위해서는, 과거 태스크 방식이 구체적으로 어떤 물리적 패널티를 발생시켰는지 현미경으로 들여다볼 필요가 있다. 이어지는 21.1.2.1.1 단원과 21.1.2.1.2 단원에서는 이 두 가지 시스템 병목 현상을 깊이 있게 해부한다.