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; }