容器的优雅退出(2):为什么是 SIGKILL?
容器的优雅退出(2):为什么是 SIGKILL?
既然不能从其他进程想办法,那我们就转变一下思路,从 init 进程入手。SIGKILL 信号我们不能捕获,但是 SIGTERM 信号是可以捕获的。那我们就捕获 init 进程接收到的 SIGTERM 信号就好了。但是,我们直接捕获会不会出错呢?原来 init 进程收到 SIGTERM 信号又做了些什么呢?
我们就必须看一下内核的代码了 ,我看的是 linux6.0 版本的内核源码。这次重要的代码文件主要是 linux-6.0/kernel/exit.c 和 linux-6.0/kernel/pid_namespace.c 这两个,代码我已经放在附件里了。
进程收到 SIGTERM 信号并使进程退出时,Linux 内核处理进程退出的入口点就是 do_exit()
函数。
linux-6.0/kernel/exit.c
void __noreturn do_exit(long code)
{
......
exit_notify(tsk, group_dead);
......
}
do_exit()
函数会释放进程的相关的资源,比如内存、文件句柄、信号量等等;会设置当前进程的状态;然后会通知其他进程本进程要退出了,也就是会调用 exit_notify()
函数。
linux-6.0/kernel/exit.c
static void exit_notify(struct task_struct *tsk, int group_dead)
{
......
forget_original_parent(tsk, &dead);
......
}
exit_notify()
函数里会通知父进程回收资源,还会告诉所有子进程选取新的父进程,函数如其名:忘记最初的父亲 :D,也就是调用 forget_original_parent()
函数。
linux-6.0/kernel/exit.c
static void forget_original_parent(struct task_struct *father,
struct list_head *dead)
{
......
reaper = find_child_reaper(father, dead);
......
}
forget_original_parent()
函数会查看本进程有没有子进程,如果没有子进程,就直接退出了,不用找新的父进程了;如果有的话,就先寻找默认的父进程,通常是当前 pid namespace 的 1 号进程;没有的话,就从其他进程当父进程。寻找默认的父进程会调用 find_child_reaper()
函数。
linux-6.0/kernel/exit.c
static struct task_struct *find_child_reaper(struct task_struct *father,
struct list_head *dead)
__releases(&tasklist_lock)
__acquires(&tasklist_lock)
{
......
zap_pid_ns_processes(pid_ns);
......
}
find_child_reaper()
函数中会判断当前进程是否是当前 pid namespace 中的 1 号进程,如果不是,就直接返回 pid namespace 中的 1 号进程,作为默认的当前进程的所有子进程的新父进程;如果是,那么就从当前进程的线程组中挑一个;如果当前既是 1 号进程,而且也没有当前进程的其他线程组,那么就会销毁当前进程的 pid namespace,调用的是 zap_pid_ns_processes()
这个函数。
linux-6.0/kernel/pid_namespace.c
void zap_pid_ns_processes(struct pid_namespace *pid_ns)
{
......
/*
* The last thread in the cgroup-init thread group is terminating.
* Find remaining pid_ts in the namespace, signal and wait for them
* to exit.
*
* Note: This signals each threads in the namespace - even those that
* belong to the same thread group, To avoid this, we would have
* to walk the entire tasklist looking a processes in this
* namespace, but that could be unnecessarily expensive if the
* pid namespace has just a few processes. Or we need to
* maintain a tasklist for each pid namespace.
*
*/
rcu_read_lock();
read_lock(&tasklist_lock);
nr = 2;
idr_for_each_entry_continue(&pid_ns->idr, pid, nr) {
task = pid_task(pid, PIDTYPE_PID);
if (task && !__fatal_signal_pending(task))
group_send_sig_info(SIGKILL, SEND_SIG_PRIV, task, PIDTYPE_MAX);
}
read_unlock(&tasklist_lock);
rcu_read_unlock();
......
}
zap_pid_ns_processes()
函数里面就会向当前 pid namespace 中的其他的进程发送 SIGKILL 信号了。为了方便理解,我画了一张图。
我们现在已经就已经知道 1 号进程的收到 SIGTERM 信号关闭其他进程的流程了,这样子我们就可以放心的编写我们的 hanlder 了。
- 点赞
- 收藏
- 关注作者
评论(0)