21.9.3.1.1. 표준 입출력(stdout, stderr) 파이프를 끊고 PX4_INFO 매크로를 통한 uORB 로거 파일(ULog)로의 출력 전환 기법
PX4 펌웨어 코딩의 엄격한 규율 중 하나는 “백그라운드 데몬(Daemon)으로 승격된 모듈은 절대로 화면(Terminal)을 탐해선 안 된다“는 것이다.
우리의 payload_autodrop 모듈이 rc.mc_apps 스크립트에 의해 start 되는 순간부터, 우리는 이 모듈이 입도 뻥긋할 수 없도록 물리적인 입출력 탯줄을 끊어내고, 오직 시스템이 허락한 전자 우체통(ULog)으로만 메시지를 밀어 넣도록 교육해야 한다.
1. 탯줄 끊기: stdout과 stderr의 폐쇄
일부 서드파티 라이브러리(Third-party Library)나 옛날 방식의 드라이버 찌꺼기 중에는 자기 마음대로 printf나 std::cout을 쏘아대는 코드들이 섞여 있을 수 있다.
데몬화(Daemonization) 과정에서 자칫 콘솔 락(Console Lock)을 물고 시스템 패닉을 일으키지 않게 하려면, 모듈이 시작점(main 함수 또는 start 루틴)에서 탯줄을 강제로 매듭지어 버리는 우아한 OS 트릭을 쓸 수 있다.
(참고: PX4의 px4_daemon::init()이나 ModuleBase 상속 구조를 철저히 지켰다면 아래 과정은 시스템이 어느 정도 알아서 처리해 주지만, 내부 이면의 원리를 이해하는 것은 매우 중요하다.)
#include <fcntl.h>
#include <unistd.h>
// [백그라운드 스레드 진입 직후의 로직 예시]
void PayloadAutoDrop::daemon_isolation() {
// 1. 시리얼 콘솔(stdout, stderr)의 파일 디스크립터를 닫아버린다.
// 더 이상 printf()가 화면으로 나가지 않는다.
close(1); // stdout 닫기
close(2); // stderr 닫기
// 2. 닫아버린 파이프를 '/dev/null' (블랙홀)로 연결해 버린다.
// 만약 어떤 멍청한 서드파티 라이브러리가 printf()를 쓰더라도 에러가 나지 않고 먼지 구덩이로 사라지게 만든다.
int fd = open("/dev/null", O_RDWR); // 새 fd는 보통 1번(stdout)으로 할당됨
dup2(fd, 2); // stderr도 fd(null)로 복제함
}
이제 우리의 모듈은 터미널의 속박에서 완전히 벗어난 완벽한 ’무소음 은둔자’가 되었다.
2. 유일한 목소리: PX4_INFO 매크로
은둔자라고 해서 벙어리가 되어서는 곤란하다. 내가 보지 않는 동안 로봇이 무슨 일을 겪었는지 개발자는 나중에 반드시 로그를 뜯어봐야 한다.
이때 입출력이 끊긴 모듈이 바깥 세상과 대화할 수 있는 유일한 통신 수단이 바로 PX4_INFO, PX4_WARN, PX4_ERR 삼형제다.
이 매크로들의 소스코드를 C++ 레벨에서 깊게 파고 들어가 보면 놀라운 구조를 발견할 수 있다.
// [PX4 플랫폼 내부의 간략화된 매크로 작동 원리]
#define PX4_INFO(FMT, ...) \
do { \
// 1. 메시지를 문자열(String) 버퍼로 포장한다.
char buffer[128]; \
snprintf(buffer, sizeof(buffer), FMT, ##__VA_ARGS__); \
\
// 2. 이 문자열을 uORB 토픽(log_message_s)에 담아 발행(Publish)한다.
log_message_s log_msg{}; \
strncpy(log_msg.text, buffer, sizeof(log_msg.text)); \
log_msg.severity = 6; /* INFO 레벨 지정 */ \
orb_publish(ORB_ID(log_message), pub_handle, &log_msg); \
} while(0)
우리가 코드에 PX4_INFO("FSM Triggered") 한 줄을 썼을 뿐인데, 사실 백그라운드에서는 이 메시지가 우리가 만든 vehicle_command 구조체처럼 하나의 uORB 메시지 조각으로 변환되어 버려진다!
이 흩뿌려진 조각들을 logger 라는 별도의 핵심 데몬 루프가 미친 듯이 쓸어 담아, SD 카드의 텍스트 파일(비행 기록, ULog) 안에 영원히 새겨 넣는다.
2.1 실전: 페이로드 모듈 로깅 최적화
우리의 모듈은 1초에 50번씩 루프(Run())를 돈다. 이때마다 “현재 고도는 10m입니다“를 미친 듯이 PX4_INFO로 쏴대면, SD 카드엔 불과 1시간 만에 수 기가바이트(GB)의 쓰레기 로그가 쌓여 I/O 병목으로 드론이 추락할 수도 있다.
따라서 데몬의 로깅은 ‘FSM 상태가 천이될 때(단 1회)’ 나, ‘치명적 에러가 발생했을 때(Max Retries 초과 등)’ 에만 제한적으로 허용해야 한다.
// [나쁜 예] 초당 50번 쏘아대는 민폐 로깅
PX4_INFO("Current altitude: %.2f", raw_altitude_m);
// [좋은 예] 상태가 바뀔 때 단 한 번의 강렬한 로깅
if (_current_state != _previous_state) {
PX4_INFO("FSM Transition: %d -> %d", (int)_previous_state, (int)_current_state);
_previous_state = _current_state;
}
데몬화된 C++ 모듈이 콘솔을 장악하지 않으면서도 안정적으로 로그를 SD 카드에 욱여넣는 방비책이 모두 완성되었다.
이제 조종사가 드론 비행을 무사히 마치고 배터리 잭(Jack)을 뽑아버리기 수 초 전.
자칫 시스템 전원이 차단되기 전 진행 중이던 메모리를 깔끔하게 공중분해시키지 못해 파일 시스템 충돌을 야기하는 대형 사고를 미연에 방지하기 위한 최후의 클린업(Cleanup) 시퀀스를 21.9.3.2장에서 완벽하게 장식하자.