地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【tini】追ってみる

github.com

Tini は、Dockerコンテナ内での "init system" の役割を果たす軽量で単純なプログラムです。通常のLinuxシステムでは、init プロセスが PID 1 としてプロセス管理を行いますが、Dockerコンテナでは、アプリケーションが直接 PID 1 として実行されるため、いくつかの問題が生じます。Tini はそれを解決するためのツールです。

全体の処理

  • 起動と初期化
  • 子プロセスの生成
  • メインループ
  • 終了処理

main

プロセスをフォークして実行し、シグナルの転送やゾンビプロセスのクリーンアップを行いながら、終了ステータスを適切に処理するということをやっています。

int main(int argc, char *argv[]) {
    pid_t child_pid;

    // Those are passed to functions to get an exitcode back.
    int child_exitcode = -1;  // This isn't a valid exitcode, and lets us tell whether the child has exited.
    int parse_exitcode = 1;   // By default, we exit with 1 if parsing fails.

    /* Parse command line arguments */
    char* (*child_args_ptr)[];
    int parse_args_ret = parse_args(argc, argv, &child_args_ptr, &parse_exitcode);
    if (parse_args_ret) {
        return parse_exitcode;
    }

    /* Parse environment */
    if (parse_env()) {
        return 1;
    }

    /* Configure signals */
    sigset_t parent_sigset, child_sigset;
    struct sigaction sigttin_action, sigttou_action;
    memset(&sigttin_action, 0, sizeof sigttin_action);
    memset(&sigttou_action, 0, sizeof sigttou_action);

    signal_configuration_t child_sigconf = {
        .sigmask_ptr = &child_sigset,
        .sigttin_action_ptr = &sigttin_action,
        .sigttou_action_ptr = &sigttou_action,
    };

    if (configure_signals(&parent_sigset, &child_sigconf)) {
        return 1;
    }

    /* Trigger signal on this process when the parent process exits. */
    if (parent_death_signal && prctl(PR_SET_PDEATHSIG, parent_death_signal)) {
        PRINT_FATAL("Failed to set up parent death signal");
        return 1;
     }

#if HAS_SUBREAPER
    /* If available and requested, register as a subreaper */
    if (register_subreaper()) {
        return 1;
    };
#endif

    /* Are we going to reap zombies properly? If not, warn. */
    reaper_check();

    /* Go on */
    int spawn_ret = spawn(&child_sigconf, *child_args_ptr, &child_pid);
    if (spawn_ret) {
        return spawn_ret;
    }
    free(child_args_ptr);

    while (1) {
        /* Wait for one signal, and forward it */
        if (wait_and_forward_signal(&parent_sigset, child_pid)) {
            return 1;
        }

        /* Now, reap zombies */
        if (reap_zombies(child_pid, &child_exitcode)) {
            return 1;
        }

        if (child_exitcode != -1) {
            PRINT_TRACE("Exiting: child has exited");
            return child_exitcode;
        }
    }
}

シグナル処理設定

if (configure_signals(&parent_sigset, &child_sigconf)) {
    return 1;
}

configure_signalsでは以下の処理をしている

  • シグナルマスクの設定
  • 特定のシグナルの除外
  • シグナルハンドラの設定
int configure_signals(sigset_t* const parent_sigset_ptr, const signal_configuration_t* const sigconf_ptr) {
    /* Block all signals that are meant to be collected by the main loop */
    if (sigfillset(parent_sigset_ptr)) {
        PRINT_FATAL("sigfillset failed: '%s'", strerror(errno));
        return 1;
    }

    // These ones shouldn't be collected by the main loop
    uint i;
    int signals_for_tini[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS, SIGTTIN, SIGTTOU};
    for (i = 0; i < ARRAY_LEN(signals_for_tini); i++) {
        if (sigdelset(parent_sigset_ptr, signals_for_tini[i])) {
            PRINT_FATAL("sigdelset failed: '%i'", signals_for_tini[i]);
            return 1;
        }
    }

    if (sigprocmask(SIG_SETMASK, parent_sigset_ptr, sigconf_ptr->sigmask_ptr)) {
        PRINT_FATAL("sigprocmask failed: '%s'", strerror(errno));
        return 1;
    }

    // Handle SIGTTIN and SIGTTOU separately. Since Tini makes the child process group
    // the foreground process group, there's a chance Tini can end up not controlling the tty.
    // If TOSTOP is set on the tty, this could block Tini on writing debug messages. We don't
    // want that. Ignore those signals.
    struct sigaction ign_action;
    memset(&ign_action, 0, sizeof ign_action);

    ign_action.sa_handler = SIG_IGN;
    sigemptyset(&ign_action.sa_mask);

    if (sigaction(SIGTTIN, &ign_action, sigconf_ptr->sigttin_action_ptr)) {
        PRINT_FATAL("Failed to ignore SIGTTIN");
        return 1;
    }

    if (sigaction(SIGTTOU, &ign_action, sigconf_ptr->sigttou_action_ptr)) {
        PRINT_FATAL("Failed to ignore SIGTTOU");
        return 1;
    }

    return 0;
}