19.8.3.2. 잘못된 폴링 타임아웃(0 또는 무한대) 설정으로 인한 CPU 점유율 100% 버그 수정
19.5 단원에서 우리는 uORB 아키텍처의 혁명적인 스레드 슬립(Sleep) 메커니즘인 px4_poll() 함수를 아주 깊게 해부하였다.
인터럽트가 걸리기 전까지 스레드를 잠재워 CPU 자원을 완전히 OS에게 양보하는(Yield) 이 우아한 블로킹 폴링(Blocking Polling) 메커니즘은, 단 하나의 치명적인 파라미터 오입력으로 전체 드론 운영체제를 일격에 질식시킬 수 있는 독이 든 사과와 같다.
개발 랩(Lab) 현장에서 SITL(시뮬레이터)을 켜고 내가 짠 커스텀 모듈을 start로 론칭시켰을 뿐인데, 갑자기 노트북의 쿨링 팬이 미친 듯이 굉음을 내며 돌고 다른 데몬들(commander, ekf2)이 모조리 멈춰버리는 끔찍한 쓰레드 데드락(Deadlock)에 직면한 적이 있다면, 그것은 100% 확률로 당신이 px4_poll 함수의 세 번째 인자인 timeout을 0으로 박아 넣었기 때문이다.
1. 최악의 런타임 참사: 타임아웃 0밀리초 (Busy Waiting)
px4_poll 함수의 세 번째 인자는 커널에게 “내가 수신하고 싶은 파이프라인에 데이터가 갱신되지 않으면, 최대 이 지정된 시간(밀리초: ms)만큼만 나를 잠재워 줘“라고 청원하는 극한의 타임아웃(Timeout) 한계선이다.
만약 초급 프로그래머가 이 타임아웃을 아래와 같이 0으로 하드코딩해버리면 어떤 살육전이 벌어질까?
// [최악의 구독자 폴링 안티 패턴: CPU 100% 점유율의 주범]
while (!should_exit) {
// 세 번째 인자를 0으로 강제 세팅: 타임아웃 없음(Never sleep)
int poll_ret = px4_poll(fds, 1, 0);
if (poll_ret == 0) { // 타임아웃에 의한 즉각 리턴
// 데이터가 안 들어왔지만, 타임아웃이 0이므로 곧바로 while문의 맨 위로 즉시 점프한다!
continue;
}
// ... (데이터 구조체 복사 로직)
}
이 무식한 코드를 작성하는 순간, 당신의 스레드는 데이터를 기다리며 sleep(잠)에 드는 것이 아니라, 초당 수억 번에 달하는 극악의 CPU 클럭 속도로 while문을 미친 듯이 돌며 VFS 커널에 “데이터 왔어? 데이터 왔어?“를 끝없이 묻어대는 이른바 ‘비지 웨이팅(Busy Waiting)’ 상태에 빠져버린다.
NuttX와 같은 하드 리얼타임 스케줄러 위에서 당신의 이 미친 커스텀 모듈 하나가 코어 1개의 CPU 점유율을 100%로 독점(Starvation)해 버리면, 정작 기체 비행의 생사를 결정짓는 메인 자이로 캘리브레이션과 자세 제어 모터 믹싱 스레드가 할당받을 틱(Tick)을 깡그리 빼앗겨 버려, 드론은 허공에서 모터 출력을 주체하지 못하고 그대로 뒤집혀 추락하게 된다.
2. 우아한 데몬화: 안전한 폴링 상한선 타설
이런 원시적인 참사를 원천 차단하기 위해, 당신의 uORB 모듈은 비즈니스 로직에 맞게 무조건 최소 수십 밀리초 이상의 타임아웃을 명시적으로 계산하여 커널에 청원해야만 한다.
만약 메인 항법 센서를 기다린다고 치더라도, 해당 센서가 고장 났을 때 내 스레드가 무한정 대기(Infinite Block, 타임아웃 -1)하는 현상을 피하기 위해서라도 1000 (1초)과 같은 명시적인 절제선(Fail-safe)을 함수 호출 단위마다 무자비하게 설정하라.
// [안전한 구독자 폴링 패턴: 1초 상한선과 예외 처리의 철칙]
while (!should_exit) {
// 1000ms(1초) 타임아웃 설정: 1초 동안 새 데이터가 안 오면 알람을 띄운다
int poll_ret = px4_poll(fds, 1, 1000);
if (poll_ret == 0) {
// [페일세이프 (Fail-safe) 방어 기동]
// 1초가 넘도록 기다렸는데 퍼블리셔가 심장 마비로 죽어 데이터가 1도 오지 않았다면,
// 이 모듈은 조용히 CPU를 양보(Yield)하거나 안전 보드(Failsafe) 궤도로 강제 진입한다.
PX4_ERR("No telemetry data received for 1 second! Triggering Failsafe Return.");
trigger_failsafe_navigation();
continue;
} else if (poll_ret > 0) {
// ... (정상적인 데이터 구조체 복사 로직)
}
}
코드가 이렇게 우아하게 수정되는 순간, 내 커스텀 루프가 px4_poll 블록에 도달하면 OS 커널이 내 스레드를 완전히 램에서 잠재우고(Sleep), 남는 남는 CPU의 방대한 컴퓨팅 파워를 commander나 navigator 데몬 등 생사가 걸린 주요 비행 스레드들에게 완벽하게 양도(Yielding)함으로써, 비로소 PX4 운영체제의 눈부신 멀티스레드 교향곡이 오작동 없이 연주되기 시작하는 것이다.