遇到一个杀不死的僵尸进程




一、认识僵尸进程


1、僵尸进程是怎么产生的?

当运行一个进程时,会产生一个父进程和一些子进程;
当子进程执行完毕后,会发送 Exit 信号然后消亡,然后它的父进程会调用(wait / waitpid)来读取它的退出状态,
如果读取成功,则将这个子进程从进程表中删除,
否则无法从进程表中删除,那么它就变成了一个僵尸进程。

当用 ps 命令观察进程状态时,可以看到这些进程的状态为 defunct。
ps aux | grep Z


2、僵尸进程是否有害?

如果僵尸进程数量巨大且长期存在的话,就相当于进程表中残留有大量僵尸进程的信息,
而这些信息是需要存储在内存的,所以会浪费资源。




二、僵尸进程常规杀法


1、kill -9 父进程pid

由于僵尸进程已经死掉了(只保留了 task_struct 结构体),而死掉的进程是无法直接 kill 的,
所以一般通过杀掉父进程来间接干掉僵尸进程。

把父进程杀掉,僵尸进程会变成孤儿进程,然后过继给1号进程,而1号进程会扫描名下子进程,把 Z 状态进程回收;

ps -ef  | grep 66046
qtest    66046      12321  99 Apr07 ?        992-23:20:31 [kvsvr] <defunct>

kill -9  12321

2、操作风险提示

杀父进程之前,建议评估操作风险, 看看父进程还有哪些关联进程,是否能容忍被杀。




三、僵尸进程暴力杀法


1、重启一下

如果僵尸进程的父进程是1号进程(ppid=1),

ps -ef  | grep 66046
qtest    66046      1  99 Apr07 ?        992-23:20:31 [kvsvr] <defunct>

那么 kill 就不管用了,只能通过 重启服务器 解决;


2、操作风险提示

重启这招比较简单粗暴但也有效,
建议根据上面服务能否容忍重启影响来决定是否可以靠重启解决。




四、为什么僵尸进程的 ppid 可以是 1 ?


1、理论上被 Init 接管的进程不会变成僵尸

在每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程,
看看是否有进程是刚刚结束的这个进程的子进程,
如果有的话, 就由 Init 进程来接管它, 成为它的父进程,从而保证每个进程都会有一个父进程。


通常来说,
一旦 init进程接管了 Z 状态的进程,就会调用 wait 将其回收的,
因此理论上被 Init 接管的所有进程都不会变成僵尸进程。


那么究竟为何会出现 ppid 是 1 的僵尸进程呢?



2、尝试推测一种可能性

回到僵尸进程产生的根源 “进程退出” 这里 ,这里尝试推测一种可能性:

进程结束会调用内核函数 do_exit,这个函数有两个关键逻辑:

do_exit()
  ->exit_notify()
     -> do_notify_parent()

2.1 作为父进程:给自己的子进程(如果存在)找一个新的父进程**

如果要退出的进程时多线程进程,则可以将子进程托付给自己的兄弟线程,
如果没有这样的线程,则托付给init进程;
总之init进程会兜底。


2.2 作为子进程:通知自己的父进程为自己释放 task_struct**

对于单线程的进程来说,这个过程也比较简单;
但对于多线程的进程就略复杂:
因为只有线程组的主线程才有资格通知父进程,
线程组的其他线程终止时,并不会通知父进程,甚至都没必要保留资源进入僵尸状态,直接调用 release_task 函数释放所有的资源就完事了。

由于父进程只认子进程的主线程,所以在线程组中,如果主线程终止了,但是如果线程组还有其他线程在运行,那么就不会通知父进程为自己释放task_struct了,直到线程组中最后一个线程退出时才会释放。

因此在用户态,是可以调用pthread_exit让主线程先退出的,但在内核态中,主线程的task_struct可能因为线程组还有其他线程在运行而需要继续留住;这种情况下,主线程就会变为Z僵尸状态,纵使init接管了主线程,也不会改变。


于是便出现了 “僵尸进程”的 ppid 是 1 的现象。

猜你喜欢

转载自blog.csdn.net/weixin_44648216/article/details/111877287
今日推荐