최종 수정일 : 2019-03-28

프로세스 종료와 재시작

init 프로세스는 init.rc 파일로부터 파싱한 서비스 리스트를 통해 자식 프로세스를 순차적으로 실행한다.

  • init이 실행하는 주요 프로세스
    • sh : 콘솔 입출력 제공하는 셸 프로그램
    • adbd : Android Debug Bridge Daemon, QEMU 에뮬레이터나 실제 기기의 상태를 관리하는 데 쓰는 툴
    • servicemanager : 안드로이드 시스템 서비스의 목록을 관리
    • vold : Volume Daemon, USB 스토리지나 SD 카드 장치를 마운트하고 관리
    • playmp3 : 부팅 사운드를 출력
  • init 프로세스가 실행하는 프로세스 중에 종료되면 시스템 동작에 영향을 미치는 것들이 존재한다.
    • 예를 들어, servicemanger 프로세스가 종료되면 프로세스 간 통신, 그래픽 출력, 오디오 출력 같은 기능을 사용할 수 없다.
    • 따라서 init 프로세스가 실행하는 프로세스는 대부분 종료되더라도 init 프로세스에 의해 재시작된다.

프로세스 재시작 코드 분석

(1) init 프로세스는 이벤트 처리 루프에서 대기

(2) 자식 프로세스가 종료되면 SIGCHLD 시그널에 대한 핸들러를 수행
시그널 핸들러는 sigchld_handler()함수이다.

// froyo\init\init.c

static void sigchld_handler(int s)
{
    write(signal_fd, &s, 1);
}

(3) sigchld_handler()함수는 SIGCHLD 시그널 번호를 받아 소켓 디스크립터에 기록한다.
시그널 번호가 기록된 signal_fd는 소켓 쌍으로 생성됐기 때문에 수신측 소켓 디스크립터인 signal_recv_fd로 시그널 번호가 전달된다.
signal_recv_fd는 POLL에 등록되어 있으므로 wait_for_one_process() 함수를 실행한다.

// froyo\init\init.c main function
    for(;;) {
        nr = poll(ufds, fd_count, timeout); // poll() 함수는 SIGCHLD 시그널 발생 시 이벤트 감시 상태에서 빠져 나와 이후의 코드를 실행한다.
        if (nr <= 0)
            continue;
        if (ufds[2].revents == POLLIN) {
            /* we got a SIGCHLD - reap and restart as needed */
            read(signal_recv_fd, tmp, sizeof(tmp));
            while (!wait_for_one_process(0))
                ;
            continue;
        }
        ...
    }        

signal_recv_fdufds[2]에 등록되어 있고 데이터 입력에 의해 이벤트가 발생했기 때문에 wait_for_one_process() 함수를 수행한다.

(4) wait_for_one_process() 함수
SIGCHLD 시그널을 발생시킨 프로세스가 가진 서비스 리스트에서 옵션을 체크
옵션이 oneshot(SVC_ONE_SHOT)이 아닌 경우 재시작 옵션(SVC_RESTARTING)을 추가한다. oneshot 옵션은 init.rc의 service 섹션에 정의되어 있다.
oneshot 옵션을 가진 프로세스는 재시작되지 않는다.

// froyo\init\init.c

static int wait_for_one_process(int block)
{
    pid_t pid;
    int status;
    struct service *svc;
    struct socketinfo *si;
    time_t now;
    struct listnode *node;
    struct command *cmd;
    while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR ); // waitpid() 함수 : 시그널을 발생시킨 자식 프로세스가 종료되면 해당 프로세스에 할당된 자원을 회수한다. pid 값을 반환하며 반환된 값은 SIGCHLD 시그널이 발생한 프로세스의 pid를 의미한다.
    if (pid <= 0) return -1;
    INFO("waitpid returned pid %d, status = %08x\n", pid, status);
    svc = service_find_by_pid(pid);  // 서비스 리스트에서 종료된 프로세스에 해당하는 서비스 항목을 가져온다.
    if (!svc) { 
        ERROR("untracked pid %d exited\n", pid);
        return 0;
    }
    NOTICE("process '%s', pid %d exited\n", svc->name, pid);
    if (!(svc->flags & SVC_ONESHOT)) { // 가져온 서비스 항목의 옵션이 SVC_ONESHOT으로 설정되어 있는지 체크한다. SVC_ONESHOT 옵션은 한 번만 실행하고 종료되는 프로세스에 설정된다. 이 옵션이 설정된 프로세스는 한 번 실행 후 재시작되지 않고 kill() 함수에 의해 종료된다.
        kill(-pid, SIGKILL);
        NOTICE("process '%s' killing any children in process group\n", svc->name);
    }
    /* remove any sockets we may have created */
    for (si = svc->sockets; si; si = si->next) { // 프로세스가 가진 소켓 디스크립터를 모두 삭제한다.
        char tmp[128];
        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
        unlink(tmp);
    }
    svc->pid = 0; // 서비스 항목이 가진 pid 값과 상태 플래그에서 프로세스가 구동중임을 나타내는 SVC_RUNNING을 제거한다.
    svc->flags &= (~SVC_RUNNING);
        /* oneshot processes go into the disabled state on exit */
    if (svc->flags & SVC_ONESHOT) { // SVC_ONESHOT 옵션이 설정된 프로세스의 플래그에 SVC_DISABLED를 설정하고 wait_for_one_process() 함수를 빠져나간다. 프로세스는 재시작되지 않는다.
        svc->flags |= SVC_DISABLED;
    }
        /* disabled processes do not get restarted automatically */
    if (svc->flags & SVC_DISABLED) {
        notify_service_state(svc->name, "stopped");
        return 0;
    }
    now = gettime();
    if (svc->flags & SVC_CRITICAL) {
        if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
            if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
                ERROR("critical process '%s' exited %d times in %d minutes; "
                      "rebooting into recovery mode\n", svc->name,
                      CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
                sync();
                __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                         LINUX_REBOOT_CMD_RESTART2, "recovery");
                return 0;
            }
        } else {
            svc->time_crashed = now;
            svc->nr_crashed = 1;
        }
    }
    svc->flags |= SVC_RESTARTING; // 현재 서비스 항목의 플래그에 SVC_RESTART를 추가한다. 
    /* Execute all onrestart commands for this service. */
    list_for_each(node, &svc->onrestart.commands) { // 재시작할 프로세스가 init.rc 파일에서 onrestart 옵션을 가지고 있는지 체크한다. onrestart 옵션은 프로세스 재시작 시 실행할 명령어를 가리킨다.
        cmd = node_to_item(node, struct command, clist);
        cmd->func(cmd->nargs, cmd->args);
    }
    notify_service_state(svc->name, "restarting");
    return 0;
}

wait_for_one_process() 함수의 실행이 완료되면 이벤트 처리 루프에서 restart_process() 함수를 실행한다.

(5) restart_process() 함수
서비스 리스트에서 SVC_RESTART 플래그를 가진 프로세스를 실행한다.
따라서 자식 프로세스가 종료되어 SIGCHLD 시그널을 발생시키더라도 이 함수를 통해 재시작된다.

// froyo\init\init.c

static void restart_service_if_needed(struct service *svc)
{
    time_t next_start_time = svc->time_started + 5;
    if (next_start_time <= gettime()) {
        svc->flags &= (~SVC_RESTARTING);
        service_start(svc, NULL);
        return;
    }
    if ((next_start_time < process_needs_restart) ||
        (process_needs_restart == 0)) {
        process_needs_restart = next_start_time;
    }
}
static void restart_processes()
{
    process_needs_restart = 0;
    service_for_each_flags(SVC_RESTARTING,
                           restart_service_if_needed);
}