21.6.2.1.2. `wq_hp_default` / `wq_lp_default`: 50Hz~250Hz 위치 제어 및 비동기 I/O 로직용 워커 분배

21.6.2.1.2. wq_hp_default / wq_lp_default: 50Hz~250Hz 위치 제어 및 비동기 I/O 로직용 워커 분배

wq_rate_ctrl이 숨 막히는 1밀리초의 사투를 벌이는 F1 레이싱 트랙이라면, wq_hp_default(High Priority)와 wq_lp_default(Low Priority) 버스 노선은 짐이 많고(메모리) 계산이 오래 걸리는(CPU Time) 일반 화물 트럭들의 전용 도로이다.

만약 여러분이 드론에 매달린 짐벌(Gimbal) 카메라를 제어하거나, 화려한 LED 쇼를 펼치거나, 복잡한 충돌 회피 알고리즘을 짠다면 바로 이 버스에 탑승해야 한다.

// 다소 무겁고 여유로운 CustomApp 모듈의 생성자
CustomApp::CustomApp() :
    ModuleParams(nullptr),
    // 중간 속도이거나 무거운 연산이 제격인 High Priority 버스 탑승!
    ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::hp_default) 
{
}

1. wq_hp_default (High Priority Default)

이름에 ’Default’가 붙어 있듯, PX4 개발자가 별다른 고민 없이 가장 무난하게 타기 좋은 국민 버스 노선이다.

  • 주요 대상: 50Hz ~ 250Hz 사이로 동작하는 미들웨어(Middleware) 모듈들.
  • 대표적 승객: mc_pos_control (멀티콥터 멀티콥터 위치 제어기), navigator (웨이포인트 비행 로직), sensors (각종 센서 데이터 저주파 취합).
  • 특징 및 혜택: 이 버스는 rate_ctrl 보다는 우선순위가 낮지만, 여전히 상당한 실시간성(Soft Real-time)을 보장받는다. 가장 큰 혜택은 넉넉한 스택(Stack) 메모리이다. 보통 4KB~8KB 이상의 스택이 할당되므로, Run() 함수 안에서 다소 복잡한 수학 라이브러리 연산을 돌리거나 크기가 큰 지역 변수를 선언해도 스택이 터지지 않는다.

만약 위치 제어기(mc_pos_control)가 실수로 rate_ctrl 버스에 올라탔다면, 그 무거운 부동소수점 행렬 연산을 처리하느라 자이로 센서 스레드가 막혀버려 드론이 즉시 추락할 것이다. 이처럼 hp_default는 시스템의 치명적인 교착 상태를 막아주는 훌륭한 격리 수용소 역할을 한다.

2. wq_lp_default (Low Priority Default)

이 버스는 픽스호크 시스템 내에서 가장 천대받는 제일 밑바닥 계급의 버스 노선이다.

  • 주요 대상: 1Hz ~ 10Hz의 굼벵이 같은 속도로 동작하거나, 언제 끝날지 모르는 하드웨어 I/O 통신 대기(Blocking)가 발생하는 모듈들.
  • 대표적 승객: logger (SD 카드에 uLog 파일 기록), tone_alarm (부저 소리 재생), battery_status (배터리 전압 주기적 체크).
  • 특징 및 한계: 이 버스의 승객들은 상위 버스(rate_ctrl, hp_default)들이 한숨 돌리며 쉬고 있을 때만 간신히 CPU 클럭을 주워 먹으며 움직인다. 따라서 이 버스 안에서는 타이밍(Timing)을 생명으로 여기는 코드를 작성해서는 절대 안 된다. Run() 함수를 호출해 주기로 한 예약 시간이 10~50ms씩 무참히 짓밟히고 지연되는 일이 일상다반사이기 때문이다.

특히 SD 카드에 데이터를 기록하는 logger 모듈의 경우, SD 카드 컨트롤러 칩의 상태에 따라 쓰기 작업이 수십 밀리초(ms) 넘게 멈춰(Blocking) 버리기도 한다. 이런 지뢰 같은 모듈을 묶어두는 곳이 바로 lp_default 워크 큐이다.

3. 전략적 분배의 미학

초보 개발자들은 종종 모듈을 하나 짜놓고 잘 동작하지 않으면 무작정 우선순위가 높은 wq_rate_ctrl 로 올려버리는 최악의 하책을 둔다. 이는 내 모듈 하나 살리겠다고 픽스호크 전체의 생명 유지 장치를 끄는 짓과 같다.

훌륭한 PX4 C++ 개발자는 **모듈의 성격을 극단적으로 분리(Decoupling)**한다.

  1. 센서에서 데이터를 읽어와서 급하게 모터로 쏘아주는 ’뼈대 로직’은 아주 가볍게 묶어 wq_rate_ctrl에 올린다.
  2. 그 결과를 파일로 저장하거나 디스플레이에 띄워주는 ’살점 로직’은 별도의 모듈로 분리하여 wq_lp_default로 내쫓은 뒤, uORB 메시지로 느긋하게 넘겨준다.

이제 버스는 잘 골라잡았다. 그렇다면 내 모듈(CustomApp)은 이 버스 안에서 구체적으로 ‘언제 한 번씩 들여다봐달라고(Scheduling)’ 타이머를 맞춰야 할까? 다음 장(21.6.3)에서 타임스탬프와 인터럽트를 이용한 주기적 실행 로직의 예술적인 타이밍 제어법을 배워보자.