僵尸进程
当一个进程退出的时候,Linux操作系统会将该进程的重型资源释放(释放CPU、释放内存,关闭打开的文件等),但是会保留该进程的一部分信息(PID、终止状态、使用的CPU时间等),因此父进程在调用 wait()
时可以得到这些信息。然而如果父进程没有调用 wait()
方法获取这个子进程的结束状态的话,那么这个子进程的状态将变为Z(zombie),一般称之僵尸进程。另外,任意子进程在退出的时候还会向父进程发送一个 SIGCHLD
信号,父进程调用 wait()
方法的时候实际就是在监听此信号,来判断是否有子进程退出。
处理僵尸进程
- 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
- 父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。另外,waitpid可以通过传递
WNOHANG
使父进程不阻塞立即返回。 - 如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
- 通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
孤儿进程
如果当前进程的父进程先于当前进程退出,那么当前进程的PPID会被重新设置为1(这也是1号进程被称为孤儿院的原因),此时当前进程成为孤儿进程。
处理孤儿进程
Linux
在Linux系统中1号进程为init进程。init进程有自动清理僵尸进程的功能,换句话说init进程会自动wait所有的僵尸进程,因此我们通常看不到孤儿进程的存在。
Docker
在Docker run命令中中如果开启 --init
开关的话1号进程也是init进程(此特性的后端实现是tini),然后再fork出来用户指定的进程,那么这个容器中所有的僵尸进程都可以放心的交给init进程来回收。否则1号进程为用户指定的进程,需要由用户指定的进程实现僵尸进程回收。
K8s
K8s上没有给出像docker一样简便的方式自动回收孤儿进程,一种方式是通过显示的方式启动tini,另一种方式就是在一号进程中实现孤儿进程的回收逻辑。
使用Golang的协程实现子进程的回收
func ReapChildren(ctx context.Context) {
c := make(chan os.Signal, 1)
signal.Notify(c, unix.SIGCHLD)
for {
select {
case <-c:
case <-ctx.Done():
fmt.Println("reaper exit")
return
}
fmt.Println("Received signal SIGCHLD")
func() {
POLL:
var status unix.WaitStatus
pid, err := unix.Wait4(-1, &status, unix.WNOHANG, nil)
switch err {
case nil:
if pid > 0 {
fmt.Println("cleanup child pid=%v, status=%v, exit_status=%v", pid, status, status.ExitStatus())
goto POLL
}
return
case unix.ECHILD:
return
case unix.EINTR:
goto POLL
default:
fmt.Println("unknown error: %v", err)
return
}
}()
}
}
附录
- 僵尸进程和孤儿进程概念了解:https://www.cnblogs.com/wuchanming/p/4020463.html
- Docker支持docker-init:https://docs.docker.com/engine/reference/run/#specify-an-init-process
- 关于K8s不支持docker-init的原因的讨论:https://stackoverflow.com/questions/50803268/kubernetes-equivalent-of-docker-run-init
- docker-init的实现:https://github.com/krallin/tini
- Golang实现tini的项目go-reap:https://github.com/hashicorp/go-reap
- Golang实现tini的项目go-reaper:https://github.com/ramr/go-reaper
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nz_nuaa@163.com