| 검색 | ?

죽어도 죽지 않는 프로세스를 위한 launcher 만들기

1.1. 개요

Posix 계열 OS는 대부분 fork() 함수를 통해서 Process를 복제할 수 있습니다. 이것을 응용하여 내가 만들었거나 남이 만든 불안정한 프로그램을 비정상 종료되어도 다시 띄워주는 죽지 않는 프로세스를 만들어 볼 수 있습니다. 보통 launcher라고 불리우기도 하는데요. 특정 조건의 프로세스를 감시하면서 조건이 발생하면 다시 띄워주거나 특정한 행동을 하도록 할 수 있는 기법이기도 합니다. 여기서는 이러한 launcher를 어떻게 만들고 응용할지 고민해보고자 예제를 만들어 봤습니다.

fork, waitpid, signal 함수를 어떻게 조합하여 내가 원하는 무적의 프로세스를 만들까 한번쯤 고민해보면 좋겠죠.

물론 비정상 종료 상황은 언젠가는 해결해야 하겠지만 그것을 해결할만한 충분한 시간이 주어지지 않는다면 이것을 먼저 고려할 필요가 있습니다. 단, 장치드라이버에 의한 문제는 좀더 복잡한 상황악화를 만들 수 있어서 또 다른 고민이 필요하기도 합니다.

1.2. 예제소스 다운로드

1.3. 소스

/*
    Copyright (C) HWPORT.COM
    All rights reserved.
    Author: JAEHYUK CHO <mailto:minzkn@minzkn.com>
*/

#if !defined(_ISOC99_SOURCE)
# define _ISOC99_SOURCE (1L)
#endif

#if !defined(_GNU_SOURCE)
# define _GNU_SOURCE (1L)
#endif

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int hwport_launcher(void);

static void mysignal_handler(int s_signum);

int main(int s_argc, char **s_argv);

static int g_is_break = 0;

int hwport_launcher(void)
{
    pid_t s_pid;

    for(;;) {
        s_pid = fork(); /* 프로세스를 생성 */
        if(s_pid == ((pid_t)(-1))) { /* fork 실패 */
            return(-1);
        }
        else if(s_pid == ((pid_t)0)) { /* 자식프로세스 부분 - 실제 우리가 구현하는 목적의 코드들이 자식프로세스에서 실행되도록 합니다. */
            /* ok. immortal process start */
            
            /* need to default signal handler ! */
#if defined(SIGBUS)            
            (void)signal(SIGBUS, SIG_DFL);
#endif
#if defined(SIGSTKFLT)
            (void)signal(SIGSTKFLT, SIG_DFL);
#endif            
            (void)signal(SIGILL, SIG_DFL);
            (void)signal(SIGFPE, SIG_DFL);
            (void)signal(SIGSEGV, SIG_DFL);
            break;
        }
        else { /* 부모프로세스 - 자식프로세스를 감시하고 비정상 종료를 감시하도록 하며 정상종료시에는 부모프로세스도 함께 종료합니다. */
            pid_t s_waitpid_check;
            int s_status = 0;
            int s_options = 0;
            int s_signum = (-1);
          
#if 1L /* DEBUG - SIGINT */ 
            (void)signal(SIGINT, SIG_IGN);
#endif
 
            (void)fprintf(stdout, "Start monitoring by hwport_launcher ! (pid=%ld)\n", (long)s_pid);
            
            /* 디버거에 의해서 step실행하게 될때 어떤 영향을 받는지 고민해봐야죠. */            
#if defined(WUNTRACED)
            s_options |= WUNTRACED;
#endif
#if defined(WCONTINUED)
            s_options |= WCONTINUED;
#endif

            do {
                s_waitpid_check = waitpid(s_pid, (int *)(&s_status), s_options); /* 자식프로세스의 종료를 기다립니다. */
                if(s_waitpid_check == ((pid_t)(-1))) { /* 뭔가 자식프로세스를 확인하기 어려운 상황 */
                    /* what happen ? */
             
                    (void)fprintf(stderr, "Waitpid failed by hwport_launcher ! (pid=%ld)\n", (long)s_pid);

                    exit(EXIT_SUCCESS);
                } 

                if(WIFEXITED(s_status) != 0) { /* 일반적인 자식프로세스 종료상태 - main함수에서 반환 또는 exit함수에 의한 종료등 */
                    /* normal exit */
                    
                    (void)fprintf(stdout, "Stop monitoring by hwport_launcher ! (pid=%long)\n", (long)s_pid);

                    exit(EXIT_SUCCESS);
                }
                else if(WIFSIGNALED(s_status) != 0) { /* Signal 발생에 의한 종료 */
                    s_signum = WTERMSIG(s_status);
                    if(
#if defined(SIGBUS)
                        (s_signum != SIGBUS) &&
#endif
#if defined(SIGSTKFLT)
                        (s_signum != SIGSTKFLT) &&
#endif
                        (s_signum != SIGILL) &&
                        (s_signum != SIGFPE) &&
                        (s_signum != SIGSEGV) &&
                        (s_signum != SIGPIPE)) { /* 비정상 종료로 볼 수 있는 signal을 제외한 나머지 signal에 의한 종료인 경우 */
                        /* normal exit */

                        (void)fprintf(stdout, "Stop monitoring by hwport_launcher ! (pid=%long, signum=%d)\n", (long)s_pid, s_signum);

                        exit(EXIT_SUCCESS);
                    }
                }
            }while((WIFEXITED(s_status) == 0) && (WIFSIGNALED(s_status) == 0)); /* 자식프로세스가 종료된게 아닌 상황이면 다시 waitpid하는 loop - 보통 디버거상태를 추적하는 중일때 이 loop가 회전합니다. */

            (void)fprintf(stdout, "Restarting by hwport_launcher ! (pid=%ld, signum=%d)\n", (long)s_pid, s_signum);

            sleep(3); /* 재시작 하기전에 약간 시간을 줘야 하는 경우가 프로그램 구현에 따라서 고려되어야 한다는 의미로... */

            /* retry launch loop */
        }
    }

    return(0);
}

static void mysignal_handler(int s_signum)
{
    (void)fprintf(stderr, "CTRL+C\n");

    g_is_break = 1;

    (void)signal(s_signum, &mysignal_handler);
}

int main(int s_argc, char **s_argv)
{
    int s_ticks;

    (void)s_argc;
    (void)s_argv;

    (void)fprintf(stdout, "BEGIN TEST...\n");

    if(hwport_launcher() == (-1)) {
        (void)fprintf(stderr, "hwport_launcher failed !\n");
    }
    /* 이제부터 비정상 정료가 발생하면 hwport_launcher호출시점부터 다시 실행됩니다. */

    (void)signal(SIGINT, &mysignal_handler); /* CTRL+C */

    for(s_ticks = 0;(s_ticks < 60) && (g_is_break == 0);s_ticks++) {
        (void)fprintf(stdout, "alive (ticks=%d)\n", s_ticks);

        if(s_ticks == 10) {
            *((unsigned long *)0) = 1234; /* segment fault ! - 강제로 비정상 종료를 위해서 Fault상황을 발생해보죠. */
        }

        sleep(1);
    }

    (void)fprintf(stdout, "END TEST...\n");

    return(EXIT_SUCCESS);
}

/* vim: set expandtab: */
/* End of source */


Copyright ⓒ MINZKN.COM
All Rights Reserved.