偶遇超级僵尸进程

你怎么死来死去都死不了啊?
其实我差点就死了,你再给我多一点点时间,我就死定了。  ----喜剧之王    

写在前面的话

僵尸进程是经常遇到的,解决起来也比较方便。但一个偶然的机会遇到了一个父进程为1号进程的的僵尸进程,这个父进程肯定是不能随意杀死的,否则会导致严重的后果,至于什么严重的后果我也没测试过,搜了一下好像也没人细说。所以我私自地给它命名为超级僵尸进程,听起来给人一种很厉害的感觉,但又略带有一些调侃,加上上面的一句喜剧之王的台词,显得饶有趣味,好吧,岔开了,进入正题。

Normal Zombie

关于普通僵尸进程

首先它是如何产生的?
一个进程终止的方法很多,进程终止后有些信息对于父进程和内核还是很有用的,例如进程的ID号、进程的退出状态、进程运行的CPU时间等。因此进程在终止时回收所有内核分配给它的内存、关闭它打开的所有文件等等,但是还会保留以上极少的信息,以供父进程使用。父进程可以使用 wait/waitpid 等系统调用来为子进程收拾,做一些收尾工作。
因此,一个僵尸进程产生的过程是:父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD信号,父进程此时应该调用wait()系统调用来获取子进程的退出状态以及其它的信息。在 wait 调用之后,僵尸进程就完全从内存中移除。因此一个僵尸存在于其终止到父进程调用wait等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中.
概述来说就是两点原因:
1.子进程终止后向父进程发出SIGCHLD信号,父进程默认忽略了它;
2.父进程没有调用wait()或waitpid()函数来等待子进程的结束。
如何发现系统中的僵尸进程?
可以运行命令–>ps -aux |grep -w ‘Z’ (这里的-w参数表示精确匹配)

如何杀掉普通僵尸进程

把父进程杀掉,僵尸进程会变成孤儿进程,然后过继给1号进程,而1号进程会扫描名下子进程,把 Z 状态进程回收;这时候僵尸进程已经退出了,只保留了task_struct结构体,所以发信号(-9等信号)去处理僵尸进程是无效的;

Super Zombie

超级僵尸进程

通过对普通僵尸进程的分析,这样看起来好像父进程为1号进程的进程不会成为僵尸进程了,因为1号进程都会时刻扫描其子进程的状态,发现是僵尸进程就会马上去释放它的资源。
但是,父进程为1号进程的进程 其实也是有可能成为僵尸进程的。下面说几种情况:
1、进程还在被其它进程使用,退出;
2、进程的子线程还在执行任务,但主线程已经死掉了(可能主线程已经被杀了,systemd停止服务时会发SIGTERM信号);
3、进程阻塞在某一IO请求上,这时控制权已交到内核手上,这时如果子进程被KILL掉,那么就成为父进程ID为1的僵尸进程,这个进程不会退出,会一直阻塞直到IO请求被满足。

应对超级僵尸进程

其实ppid=1的僵尸进程可以不用去管他,因为它迟早会被1号进程回收的如果有很多僵尸进程除外.并且绝大部分ppid=1的僵尸进程是暂时的:
1、当进程被跟踪调试完,则会自动被回收掉的;
2、其他子线程组的线程执行完后会自动退出,僵尸进程会被回收;
3、这个可能会一直挂着,如果阻塞的io永远没有到达;

总之,遇到少量的僵尸进程,可以不需要特意的去处理,只需要查看下根源,看看是否有潜在的bug就可以;