21.6. Work Queue 스레드 풀 및 스케줄러(Scheduler) 제어
PX4 모듈 해부학의 마지막 퍼즐은 바로 그 모듈을 **‘어디서, 어떻게 굴게 할 것인가(Scheduling)’**에 대한 이야기이다.
지금까지 우리는 모듈을 생성하는 법(ModuleBase), 파라미터를 얽어매는 법(ModuleParams)을 배웠지만, 이 모듈이 운영체제(NuttX 또는 Linux) 위에서 스스로 CPU 자원을 할당받아 살아 숨 쉬는 생명체(Thread)가 되기 위해서는 마지막 관문이 남아있다.
1. 모듈 스레드의 진화: 독고다이(Task)에서 스레드 풀(Work Queue)로
과거 초창기 PX4 시절(NuttX 초기 버전), 모든 모듈은 task_spawn이라는 함수를 통해 자신만의 독립적인 프로세스(정확히는 NuttX Task)를 통째로 하나씩 부여받았다.
센서 모듈 하나에 태스크 하나, 제어 모듈 하나에 태스크 하나… 수십 개의 모듈이 각자의 태스크(스레드)를 가지다 보니 픽스호크 내부에는 순식간에 50~100개의 태스크가 중구난방으로 돌아가기 시작했다.
이러한 1 모듈 = 1 태스크 방식은 치명적인 단점들을 노출했다.
- 메모리 낭비: 1개의 태스크가 생성될 때마다 최소 1~2KB의 스택(Stack) 메모리가 고정적으로 잡힌다. 수십 개의 태스크가 떠 있으면 픽스호크의 256KB 남짓한 소중한 RAM이 순식간에 동난다.
- 컨텍스트 스위칭(Context Switching) 오버헤드: 수십 개의 스레드 사이를 실시간으로 오가며 스케줄링을 해줘야 하는 운영체제(커널)의 부담이 극도로 심해져, 정작 중요한 제어 알고리즘 연산에 쓸 CPU 클럭을 낭비하게 되었다.
이 거대한 비효율을 타파하기 위해 PX4 코어 아키텍처 팀이 짜낸 혁명적인 아이디어가 바로 Work Queue (워크 큐) 스레드 풀(Thread Pool) 시스템이다.
2. Work Queue: 버스(Bus)에 올라타라
Work Queue 시스템의 핵심 철학은 **“개인 자가용(독립 Task) 구비 금지, 무조건 공공 버스(Work Queue)를 이용할 것!”**으로 요약된다.
PX4 부팅 시, 시스템은 픽스호크의 RAM 여건에 맞춰 미리 5~6개 남짓의 큼직한 ’작업 버스(Thread)’만 미리 생성해 둔다.
- wq:rate_ctrl (최고 속도 버스, 자세 제어용)
- wq:nav_and_cmds (중간 속도 버스, 내비게이션용)
- wq:hp_default (고우선순위 일반 버스)
- wq:lp_default (저우선순위 잡일 버스, 로깅 등)
이제 우리가 만드는 CustomApp 모듈은 스스로 태스크를 생성하여 스택 메모리를 낭비하는 짓을 멈춰야 한다.
대신, 내 모듈의 Run() 함수(실제 연산 덩어리)를 뜯어서 하나의 예쁜 **작업 보따리(Work Item)**로 포장한 뒤, 내가 원하는 속도의 버스(예: wq:rate_ctrl) 짐칸에 휙 던져 넣기만 하면 된다.
그러면 그 버스가 목적지(Cycle)를 돌 때마다 멈춰 서서 내 보따리를 끄집어내어 한 번씩 실행시켜 주는 방식이다.
이 놀라운 C++ 추상화 덕분에 수십 개의 모듈이 단 몇 개의 스레드 공간 안에서 사이좋게(Multiplexing) 돌아가게 되며, 스택 메모리 낭비와 컨텍스트 스위칭 오버헤드가 극적으로 소멸되었다.
그렇다면 내 C++ 모듈을 이 ’작업 보따리’로 변신시키기 위해 우리가 상속받아야 하는 또 하나의 마법 클래스, **px4::WorkItem**의 구조에 대해 다음 장(21.6.1)에서 디테일하게 알아보자.