Tini 源码分析(1)
(奇怪,我是一个写 Go 的,为什么要来分析 C 代码的项目啊!)
Tini 简介
Tini 是一个超轻量级的 init 进程管理器,被设计作为容器的 1 号进程。
Tini 只会做以下的事情:
- 只生成一个子进程(这意味着 Tini 应该运行在容器中),并等待子进程退出
- 收割僵尸进程
- 执行信号转发
Tini 只能管理一个进程,容器的最佳实践一般都是一个容器即一个进程,因此 Tini 在容器化场景足够使用了。
Tini 的使用
如果你使用的是 Docker 1.13 或更高版本, Tini 已包含在 Docker 中。包括所以版本的 Docker CE。如果要启用 Tini,只需将 --init
参数传递给 Docker run 即可。
Tini 的优势
使用 Tini 有几个好处:
- 它可以保护你应用免受僵尸进程的侵害,这可能会随着时间而推移,使你的整个系统缺少可分配的 PID(并使其不可用)。
- 它确保默认信号处理程序适用于你运行的 Docker 镜像中的应用。例如,使用 Tini 可以让 SIGTERM 正确终止你的进程,即使没有显式的编写信号处理程序。
- 它运行起来是完全透明的。没有 Tini 的 Docker 镜像和有 Tini 的Docker 镜像使用起来是没有区别的。
如果你想详细了解 Tini 的好处,可以查看:What is advantage of Tini?
Tini 源码分析
我会从程序在 main 函数中执行的顺序,来一步一步的分析源码。每一个函数我都会贴上源码,然后进行分析。我把 main 函数的大概流程画了出来,大家可以参考一下。文章太长发不了,只能分割成两章了,这是第一章。
int main(int argc, char *argv[]) {
// 用于存放 子进程的pid
pid_t child_pid;
// 这些被传递到函数中,以获得一个exitcode。
int child_exitcode = -1; // 这不是有效的exitcode;让我们判断子进程是否已经退出。
int parse_exitcode = 1; // 默认情况下,如果解析失败,则返回1。
/* 解析命令行参数 */
char* (*child_args_ptr)[];
int parse_args_ret = parse_args(argc, argv, &child_args_ptr, &parse_exitcode);
if (parse_args_ret) {
return parse_exitcode;
}
/* 解析环境 */
if (parse_env()) {
return 1;
}
/* 配置信号 */
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;
}
/* 当父进程退出时,该进程上会触发的信号。 */
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
/* 我们可以正常收割僵尸进程吗?如果没有,警告。 */
reaper_check();
/* 创建子进程 */
int spawn_ret = spawn(&child_sigconf, *child_args_ptr, &child_pid);
if (spawn_ret) {
return spawn_ret;
}
free(child_args_ptr);
while (1) {
/* 等待信号并转发信号 */
if (wait_and_forward_signal(&parent_sigset, child_pid)) {
return 1;
}
/* 现在,收割僵尸进程 */
if (reap_zombies(child_pid, &child_exitcode)) {
return 1;
}
if (child_exitcode != -1) {
PRINT_TRACE("Exiting: child has exited");
return child_exitcode;
}
}
}
解析命令行参数
// 解析命令行参数
int parse_args(const int argc,