容器的优雅退出(2):为什么是 SIGKILL?

举报
东坡爱吃肘子 发表于 2022/11/10 12:41:57 2022/11/10
【摘要】 主进程收到 SIGTERM 信号,之后又发生了什么事情,才让子进程们收到 SIGKILL 信号的呢?让我们看一下 kernel 源码吧。

容器的优雅退出(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 信号了。为了方便理解,我画了一张图。

exit() 如何 SIGKILL 子进程

我们现在已经就已经知道 1 号进程的收到 SIGTERM 信号关闭其他进程的流程了,这样子我们就可以放心的编写我们的 hanlder 了。

  • 17.25KB 下载次数:1

    附件下载

  • 2.zip 17.25KB 下载次数:1
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。