僵尸进程
- 怎样产生僵尸进程的: 一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。 网络原因有时也会引起僵尸进程,
- 僵尸进程有害吗? 不会。由于僵尸进程并不做任何事情, 不会使用任何资源也不会影响其它进程, 因此存在僵尸进程也没什么坏处。 不过由于进程表中的退出状态以及其它一些进程信息也是存储在内存中的,因此存在太多僵尸进程有时也会是一些问题,比如会影响服务器的性能。
signal(SIGCHLD, SIG_IGN);
忽略SIGCHLD信号,这是一个常用于提升并发服务器性能的技巧,因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设置为忽略,可让内核把僵尸进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。 - 如何防止僵尸进程?
- 让僵尸进程成为孤儿进程,由init进程回收;(手动杀死父进程)
- 调用fork()两次;
- 捕捉SIGCHLD信号,并在信号处理函数中调用wait函数;
代码实例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void sig_handler(int signo)
{
printf("child process deaded, signo: %d\n", signo);
wait(0);// 当捕获到SIGCHLD信号,父进程调用wait回收,避免子进程成为僵尸进程
}
void out(int n)
{
int i;
for(i = 0; i < n; ++i)
{
printf("%d out %d\n", getpid(), i);
sleep(2);
}
}
int main(void)
{
// 登记一下SIGCHLD信号
if(signal(SIGCHLD, sig_handler) == SIG_ERR)
{
perror("signal sigchld error");
}
pid_t pid = fork();
if(pid < 0)
{
perror("fork error");
exit(1);
}
else if(pid > 0)
{
// parent process
out(100);
}
else
{
// child process
out(10);
}
return 0;
}
```
4. **如何找出僵尸进程呢?** `ps aux | grep Z`or `ps -ef | grep defunct`
5. **解决方法:**
* 设置SIGCLD信号为SIG_IGN,系统将不产生僵死进程。
* 用两次fork(),而且使紧跟的子进程直接退出,是的孙子进程成为孤儿进程,从而init进程将负责清除这个孤儿进程。
* 重启服务器电脑,这个是最简单,最易用的方法,但是如果你服务器电脑上运行有其他的程序,那么这个方法,代价很大。所以,尽量使用下面一种方法。
* 找到该defunct僵尸进程的父进程,将该进程的父进程杀掉,则此defunct进程将自动消失。 如何找到defunct僵尸进程的父进程?很简单,一句命令就够了:`ps -ef | grep defunct_process_pid`
* 正常情况下我们可以用 SIGKILL 信号来杀死进程,但是僵尸进程已经死了, 你不能杀死已经死掉的东西。 因此你需要输入的命令应该是`kill -s SIGCHLD pid` 。 将这里的 pid 替换成父进程的进程 id,这样父进程就会删除所有以及完成并死掉的子进程了。
### 在服务器上发现了僵尸
```shell
root@ubuntu16-desktop:~# ps aux | grep Z
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1354 0.0 0.0 14228 944 pts/8 S+ 06:13 0:00 grep --color=auto Z
root 3572 0.0 0.0 0 0 ? Z Mar05 0:00 [create-topics.s] <defunct>
root 4120 0.0 0.0 0 0 ? Z Mar05 0:00 [create-topics.s] <defunct>
root 4696 0.0 0.0 0 0 ? Z Mar05 0:00 [create-topics.s] <defunct>
这是不是我起了kafka的docker容器之后才出现的,出现原因我现在还不知道,待补充,pid竟然还会变??
我试了下,既然处理僵尸进程的方法是把父进程关掉,就行了,我一看这三个僵尸进程的父进程就是三个kafka容器,那么我docker-compose down关掉就ok了,试了试,果然这三个僵尸进程没有了,重启之后,发现还是有这三个僵尸进程。。
经过测试发现,这是这个docker镜像本身的问题,所以我像官方提了issue。
https://github.com/wurstmeister/kafka-docker/issues/497,大意是说因为create-topic这个脚本放在了后台运行,能不能用disown这个命令处理,有待进一步实验。
那么这样又引来另外一个问题,如果不能关闭父进程怎么办?
- 实现一个内核线程,专门实现模块init函数的逻辑,需要干掉的僵尸进程号通过procfs传入内核,然后在write例程中唤醒回收僵尸进程的内核线程;
- 实现一个用户态进程U,挂载一个信号A的处理函数,内部实现waitpid,通过procfs传入或者通过netlink传入内核的僵尸进程号代表的进程过继给用户态进程U,然后向U发送信号A;
/dev/mem的机器码编程或者直接释放僵尸进程的task_t。
在/proc/
/目录中加入kill-if-jiangshi文件,写入1如果该进程是僵尸,那么就调用上述模块的逻辑杀死它