21.9.3.1. 모듈의 데몬 분리 및 I/O 리다이렉션

21.9.3.1. 모듈의 데몬 분리 및 I/O 리다이렉션

PX4 플랫폼에서 C++ 모듈을 작성할 때 가장 헷갈리기 쉬운 부분 중 하나가 바로 ’로그(Log) 출력’이다.
학부생 시절 std::cout << "Hello World" << std::endl; 이나 C 언어의 printf() 함수를 숨 쉬듯이 써왔던 개발자라면, 픽스호크 펌웨어 코딩에서도 버릇처럼 저 함수들을 사용하여 터미널에 메시지를 띄우려 할 것이다.

하지만 커널 안에서 백그라운드로 도는 데몬(Daemon) 모듈에게 printf()는 맹독과도 같다.

1. 표준 입출력(Standard I/O)의 함정

NuttX와 같이 자원이 극도로 제한된 RTOS 환경에서 데몬(Daemon)으로 분리된 프로세스는 더 이상 타이핑을 치고 화면을 보여주는 콘솔(시리얼 포트)에 연결되어 있지 않다.
rcS 부트 스크립트를 통해 백그라운드로 실행된 순간, 이 모듈의 입출력 파이프(stdin, stdout, stderr)는 물리적인 화면이 아니라 허공을 맴돌게 된다.

이 상태에서 모듈이 순진하게 printf("FSM Triggered\n");를 호출하면 벌어지는 일은 다음과 같다.

  1. 운영체제는 콘솔 버퍼의 락(Lock)을 잡기 위해 대기한다.
  2. 하지만 데몬은 콘솔 소유권이 없으므로 버퍼가 영원히 꽉 막힌다.
  3. 결국 FSM 루프 전체가 printf 단 한 줄 때문에 블로킹(Blocking) 되어버리고, 투하 시점은 수십 초 이상 지연되며 드론의 실시간 제어(Hard Real-Time) 규약이 박살 난다.

2. PX4 전용 로깅 매크로: PX4_INFO, PX4_WARN, PX4_ERR

이 참사를 막기 위해 PX4 아키텍트들은 printf()의 사용을 시스템 단에서 엄격하게 억제하고, 대신 uORB 기반의 비동기 로깅 매크로를 창조해 냈다.

소스 코드 전반에서 빈번하게 보았던 PX4_INFO() 매크로의 내부 작동 원리는 이렇다.

// [일반적인 C++ 프린트] - 절대 사용 금지!
// printf("Target Altitude: %f\n", target_alt);

// [PX4 표준 백그라운드 프린트] 
PX4_INFO("Target Altitude: %.1f", (double)target_alt);

이 매크로가 호출되면 메시지는 콘솔(UART) 번역기로 직접 떨어지는 것이 아니라, ulog 라는 이름의 보이지 않는 백그라운드 우체통(uORB 토픽)으로 깔끔하게 포워딩된다.
이 리다이렉션(Redirection) 우회로 덕분에 우리의 루프는 단 1 마이크로초의 딜레이도 발생하지 않고 즉시 다음 연산으로 넘어간다.

3. ULog 파일로의 치환 기법

uORB 버스로 퍼져나간 이 로그 조각들을 주워 담는 역할은 별도로 돌아가는 logger 데몬이 담당한다.
logger 모듈은 비행하는 내내 이 로그들을 주워 모아, 나중에 컴퓨터를 꽂았을 때 .ulg (ULog 파일) 형태로 드론의 마이크로 SD 카드에 박제해 버린다.

비행 중에 발생했던 모든 PX4_ERR("Failed to Open!") 메시지들은 조종사 눈앞의 콘솔 창이 아니라, 이 ULog 파일 깊숙이 텍스트 형태로 영구 보존된다.
따라서 비행기 추락 후 블랙박스를 분석하는 엔지니어는 굳이 디버깅 터미널을 연결하지 않았었더라도 로그 파일을 뜯어서 “아, 이 시점에 모듈이 에러를 뿜고 죽었구나” 하고 부활의 단서를 찾아낼 수 있게 된다.

자, 이제 데몬의 분리와 출력의 철학까지 모두 마쳤다. 우리 모듈은 이제 영원히 안정적으로 숨 쉬며 데이터를 남긴다.
그렇다면 비행이 모두 끝나고, 기체의 전원 케이블을 뽑는 그 폭풍 전야의 순간! 이 불멸의 데몬이 쥐고 있던 메모리 자원들을 어떻게 공중분해시키고 안전하게 육신을 떠날 수 있는지, 그 최후의 의식인 Shutdown 해제 로직을 마지막 장인 21.9.3.2.1에서 다루어 보자.