19.3.4.2. 모듈 종료 시그널(SIGTERM/SIGINT) 수신 시 안전한 종료 로직(Graceful Shutdown)

19.3.4.2. 모듈 종료 시그널(SIGTERM/SIGINT) 수신 시 안전한 종료 로직(Graceful Shutdown)

지긋지긋한 VFS 링 버퍼 폭파(orb_unadvertise) 로직 클린업 코드를 무사히 소스코드 끄트머리에 심어두었다면, 퍼블리셔(Publisher) 데몬 구현의 마지막 종지부 퍼즐은 바로 **“이 미친 듯이 도는 스레드가 외부 스케줄러의 죽음 명령(Kill Signal)을 어떻게 신사적으로 알아채고 자신의 짐을 스스로 싸서 우아하게 퇴장하느냐”**이다.
앞선 기초 단원들에서 우리는 데몬의 메인 스레드 무한 루프 감옥을 외부에서 부수고 탈출시키기 위한 유일한 커널 킬 스위치(Kill-switch)로 전역 변수 bool thread_should_exit = false;를 쉘에 세팅해 두었었다.

이 단원에서는 픽스호크 비행 제어기 콘솔 터미널(NSH)이나 외부의 치명적인 와치독(Watchdog) 스케줄러가 우리 모듈 스레드를 향해 던지는 종료 시그널 커맨드를 어떻게 안전하게 인터셉트하고, 메모리가 찢겨나가는 프로세스 강제 종료(Kill -9)라는 비참한 운명 대신 ‘남에게 피해주지 않고 스스로 모든 락(Lock)을 푼 뒤 조용히 사라지는’ 완전한 시스템 Graceful Shutdown(우아한 종료) 통제권을 손에 넣는지 역해부한다.

1. NSH 터미널에서의 "stop" 커맨드 문자열 파싱과 플래그 킬링 폭파

우리가 저 아래 19.1.3 단원에서 임시 포장으로 짜두었던 px4_uorb_example_main(int argc, char *argv[]) 프로세스 진입점(Entry Point) 함수 껍데기 내부를 다시 해커의 눈으로 살펴보자. NuttX 커널 스케줄러는 단순히 우리 바이너리를 메모리에 올려 포크(Fork)하는 것을 넘어, 사용자가 터미널 콘솔에서 공격적으로 입력한 인자열(Argument)들을 운영체제 관례에 따라 argv 파라미터 배열로 때려 넘겨준다. 이 거친 문자열 파편들 중 "stop" 이라는 텍스트를 정확하게 낚아채어 파싱(Parsing)해내는 논리 분기를 가장 무자비하게 쳐야만 한다.

int px4_uorb_example_main(int argc, char *argv[])
{
    // ... 인자 개수 유효성 검사 및 "start" 분기 검사 등 기존 로직 ...

    // [치명적인 시스템 종료 시그널 강제 인터셉트 분기]
    if (!strcmp(argv[1], "stop")) {
        // 1. 데몬이 이미 죽어있는데 두 번 죽이려는 헛발질 스케줄링 명령어인지 방어적 확인
        if (!thread_running) {
            PX4_WARN("px4_uorb_example is not running! Cannot kill a ghost.");
            return -1;
        }

        // 2. [가장 폭력적이고 위대한 타격] 백그라운드 데몬 메인 루프를 깨트리기 위한 유일무이한 킬 스위치 기폭
        thread_should_exit = true;

        // 3. (중요) 스위치를 내렸다고 바로 콘솔이 종료해버려선 안 된다. 
        // 칩셋 뒤편의 백그라운드 워커 스레드가 완전히 루프를 벗어나 "스스로 목숨을 끊을 때까지" 지퍼를 채우고 기다려준다 (Join 동기화 흉내)
        PX4_INFO("px4_uorb_example daemon is gracefully exiting...");
        while (thread_running) {
            px4_usleep(10000); // 10ms 단위로 스레드 시신(?) 수습을 인내심 있게 대기
        }
        
        // 스레드 점멸 확인 완료!
        PX4_INFO("px4_uorb_example has been successfully and safely stopped.");
        return 0; // 데몬 관제소 정상(0) 코드 반환
    }
    
    // ... "start" 커맨드 분기 및 태스크(스레드) 물리적 창조 로직 ...
}

2. 안전한 프로세스 종료(Graceful Shutdown)가 PX4 생태계에 내리는 축복의 의미

이 고작 몇 줄 안 되는 짧고 단순한 "stop" 텍스트 파싱 및 불리언(Boolean) 플래그 셋업 로직 하나가, 거대하고 위태로운 PX4 펌웨어 군집 운영체제 내에서 얼마나 파괴적이고 강인한 런타임 안전망을 쳐주는지 개발자는 뼛속 깊이 깨달아야 한다.

만약 이 C++ 로직 통제권 없이, 당신이 리눅스 구경꾼 시절 습관처럼 콘솔에서 쓰던 무식한 SIGKILL(Kill -9) 시스템 콜로 스레드의 모가지를 메모리 상에서 단칼에 싹둑 잘라버렸다면 어떻게 될까? 스레드가 미친 듯이 계산을 수행 중이던 메인 함수 주소 레지스터는 커널에 의해 그 즉시 강제 폭파 파괴되지만, 가장 끔찍한 것은 바로 앞선 19.3.4.1 단원에서 타설했던 생명선인 orb_unadvertise() 함수가 단 한 번 실행될 마이크로초 단위의 시분할(Time-slice) 기회조차 영영 얻지 못하게 된다는 것이다.
따라서 힙(Heap)과 커널 트리에 무자비하게 뚫려있던 VFS 파일 핸들과 핑퐁 미들웨어 버퍼, 스레드 TCB(Task Control Block) 등은 주인을 잃은 악령 좀비(Zombie) 메모리가 되어, 보드를 하드웨어적으로 재부팅(Reboot)하기 전까지 칩셋을 영원토록 야금야금 갉아먹게 된다. 결국 OOM 커널 패닉 대참사로 피를 보게 될 것이다.

하지만 위 C++ 아키텍처 코드처럼 전역 단위의 thread_should_exit 깃발(Flag)을 우아하고 젠틀하게 들어 올리면, 무자비하게 메인 루프를 돌던 백그라운드 스레드는 진행 중이던 현재의 퍼블리싱 사이클을 무사히 마친 후, 다음번 머리 while 턴에서 스스로 루프 조건문을 확인하고 자발적으로 궤도를 이탈하게 된다.
그리고 자연스럽고 고상하게 코드 하단 블록에 박혀있던 orb_unadvertise() 자원 반환 코드를 “스스로의 발로 걸어가” 실행 타격한 뒤 메모리를 깨끗이 소독하고 평온하게 생을 마감한다.

이것이 메모리 릭(Leak) 0%와 시스템 구동율 99.999%를 달성하기 위해, PX4 코어 아키텍트 해커들이 강제하는 가장 거룩하고 기계적인 C++ 임베디드 모듈 종료 스케줄링 아키텍처의 정수이다. 당신의 모듈은 이제 영생과 영멸의 통제권을 완벽하게 손에 쥐었다.