前言
最近感觉自己对于容器这块的理解好像仅限于 docker 相关的使用,因此想深入的了解学习一下关于容器的东西,还有就是想借学习容器的时候进一步了解一下 Linux,今天先来简单记录一下容器进程侧相关的笔记。
学习内容源自:极客时间容器实战高手课
容器进程
init 进程
通俗的来说就是 linux 启动之后第一个启动的用户态进程,也就是 pid 1,随便看一台 Linux 上都能够看到 pid 1 的进程
这里可以看到 init 是一个软连接指向 systemd,Linux 的其他进程都是由它创建出来的
那么回过头来看容器,一旦容器建立了自己的 Pid Namespace(进程命名空间),这个 Namespace 里的进程号也是从 1 开始标记的。所以,容器的 init 进程也被称为 1 号进程。
课程中从 kill pid 1 进程的结果展开讨论,说实话我之前完全没考虑过这个问题
比如说下面,我希望让 jupyter notebook 重启,那么我之前都是直接 kill 1 ,但是并没有了解后续的逻辑比如为什么 kill -9 不行但是 kill 可以,因为在我的理解中 kill -9 应该是更加彻底才对
所以针对这个问题需要学习一下 Linux 中关于进程和信号的一些东西
信号
在平时使用的时候我们经常会用到 kill -9 pid ,这里的 9 其实就是信号,我们可以通过 kill -l 来列出所有信号
信号可以理解为进程收到的通知,进程有不同的状态,当进程收到信号之后进程会根据收到的信号来做出对应的状态转换(也有可能不做状态转换)
在文中主要介绍的两个信号就是 SIGTERM(15)和 SIGKILL(9)这两个信号,对应的其实就是 kill pid 和 kill -9 pid,SIGTERM这个信号就是 Linux kill 命令缺省 (default) 发出的
上面说过当进程收到信号之后会做一些状态的转换,我这边理解为的是状态机,主要是下面三个选择
- 忽略(ignore):就是对这个信号不做任何处理,但是有两个信号例外,对于 SIGKILL 和 SIGSTOP (这个信号同样也是用于进程停滞的)这个两个信号,进程是不能忽略的。这是因为它们的主要作用是为 Linux kernel 和超级用户提供删除任意进程的特权。
- 捕获(catch):这个是指让用户进程可以注册自己针对这个信号的 handler,同样针对 SIGKILL 和 SIGSTOP 信号也是不能添加 handler 的,即不能有自己的处理代码
- 缺省(default):默认值,比如 kill pid 这里的缺省值就是 SIGTERM
所以这里回到开头,我们的 kill -9 1 其实就是对 pid 为 1 的进程发了一个 SIGKILL,kill 1 就是对 pid 为 1 的进程发了一个 SIGTERM,至于进程对这两者的信号有不同的响应就需要继续看了,因为上面我们提过 SIGKILL 信号对于进程来说是不能忽略的那么正常情况下 pid 1 的进程应该是要接受到信号并且退出的,但是却没有
所以需要去看一下 Linux 内核中是如何处理的
在 Linux 中执行 kill 命令,其实是调用了内核函数 sys_kill(),而内核在决定把信号发送给 1 号进程的时候,会调用 sig_task_ignored() 这个函数来做个判断,它会决定内核在哪些情况下会把发送的这个信号给忽略掉。如果信号被忽略了,那么 init 进程就不能收到指令了。
这里和我们本文相关的主要是第二个 if 判断,可以看到有三个条件,只有三个条件都满足了才不会将信号发送给进程
-
!(force && sig_kernel_only(sig))
如果在同一 namespace 下那么 force 为 0 亿以及 & 关系所以为 true -
handler == SIG_DFL
SIG_DFL 是系统的缺省 handler,这个缺省的 handler 就叫作 SIG_DFL,由于 SIGKILL 是不能被捕获的,所以就是默认的 handler对于 SIGTERM 来说,只要用户不注册 handler 那么这个条件也是成立
-
t->signal->flags & SIGNAL_UNKILLABLE
代表进程必须为 SIGNAL_UNKILLABLE 标签 ,文中说到只要 pid 为 1 就会有这个标签
kernel/fork.c
if (is_child_reaper(pid)) {
ns_of_pid(pid)->child_reaper = p;
p->signal->flags |= SIGNAL_UNKILLABLE;
}
static inline bool is_child_reaper(struct pid *pid)
{
return pid->numbers[pid->level].nr == 1;
}
所以如果我们要 kill init 进程的话有几个办法
- 不同namespace ,宿主机 kill 容器 pid (不现实,生产环境往往没有宿主机权限)
- 给进程注册 handler 同样不现实
我们可以通过 cat /proc/1/status | grep SigCgt 来查看是否注册 handler , 如果结果为 0000000000000000 那么就说明是默认缺省 handler 我们是可以 kill 的