微信搜索“编程笔记本”,获取更多信息
------------- codingbook2020 -------------
前面我们学习了进程的相关知识,今天我们继续学习进程中的一个重要知识点:僵尸进程。
1. 什么是僵尸进程
我们知道,除了初始化进程 init (PID=1) 以外,所有的进程都不是完全孤立存在的,它有其父进程和兄弟进程。所谓僵尸进程,就是当子进程退出时,父进程尚未结束,而父进程又没有对已经结束的子进程进行回收。此时,这样的子进程就成了僵尸进程。
考虑另外一种情况,当一个进程使用 fork() 产生了一个子进程,当子进程尚未结束时,父进程已经退出,则此时的子进程就成了孤儿进程,孤儿进程会被 init 进程收养,当孤儿进程退出时,由 init 进程负责对其进行回收。
下面我们就来亲自制造一个僵尸进程(test.c
):利用 fork() 分叉出一个子进程,然后让子进程退出,父进程继续运行,这样子进程便成了僵尸进程。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
printf("fork failed\n");
return -1;
} else if (pid == 0) {
printf("this is son process with PID=%d\n", getpid());
printf("son process is over\n");
} else {
printf("this is father process with PID=%d\n", getpid());
while (1) { // 父进程不会结束
usleep(100);
}
}
return 0;
}
编译运行:
jincheng@jincheng-PC:~/Desktop$ gcc -o test test.c
jincheng@jincheng-PC:~/Desktop$ ./test
this is father process with PID=5238
this is son process with PID=5239
son process is over
使用 top
指令查看僵尸进程:
jincheng@jincheng-PC:~/Desktop$ top
top - 08:17:26 up 15 min, 1 user, load average: 0.59, 0.35, 0.32
Tasks: 153 total, 9 running, 105 sleeping, 0 stopped, 1 zombie
我们可以看到 1 zombie
,这就说明系统进程中存在一个僵尸进程,即我们方才创建的。
还有另外一种方式查看僵尸进程,使用 ps -aux |grep Z
:
jincheng@jincheng-PC:~/Desktop$ ps -aux |grep Z
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
jincheng 4018 0.0 0.0 29856 256 ? SN 08:02 0:00 /usr/bin/fcitx-dbus-watcher unix:abstract=/tmp/dbus-wiOTCvZbc4,guid=067410519f382c22f4c811d25e697c23 4014
jincheng 5239 0.0 0.0 0 0 pts/0 Z+ 08:25 0:00 [test] <defunct>
jincheng 5423 0.0 0.0 14536 984 pts/1 S+ 08:25 0:00 grep Z
这种方式就更加明了了,我们可以清楚地看到僵尸进程的 PID ,正如我们预期的那样,这个僵尸进程的 PID 和 我们 fork 出来的子进程 PID 相同,也就是说,这和僵尸进程就是方才的子进程。
2. 僵尸进程的危害
由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束. 那么会不会因为父进程太忙而来不及 wait 子进程,或者说不知道子进程什么时候结束,而丢失子进程结束时的状态信息呢?
不会。因为 UNIX 提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息,就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号 the process ID,退出状态 the termination status of the process,运行时间 the amount of CPU time taken by the process 等)。直到父进程通过 wait/waitpid
来取时才释放。
这样就导致了问题,如果进程不调用 wait/waitpid 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。此即为僵尸进程的危害,应当避免。
3. 如何避免僵尸进程的产生
- 父进程通过 wait 和 waitpid 等函数等待子进程结束,这会导致父进程挂起。
/* test.c */
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
int main() {
int status;
pid_t pid;
pid = fork();
if (pid < 0) {
printf("fork failed\n");
} else if (pid == 0) {
printf("this is son process with PID=%d\n", getpid());
printf("son process is over\n");
} else {
printf("this is father process with PID=%d\n", getpid());
usleep(100);
waitpid(pid, &status, 0); // 等待子进程
printf("father process is over\n");
}
return 0;
}
编译运行:
jincheng@jincheng-PC:~/Desktop$ gcc -o test test.c
jincheng@jincheng-PC:~/Desktop$ ./test
this is father process with PID=5645
this is son process with PID=5646
son process is over
father process is over
查看是否存在僵尸进程:
jincheng@jincheng-PC:~/Desktop$ top
top - 08:38:51 up 37 min, 1 user, load average: 0.03, 0.05, 0.09
Tasks: 149 total, 1 running, 110 sleeping, 0 stopped, 0 zombie
- 我们知道,当一个进程结束时会将 SIGCHLD 信号发送给其父进程,系统默认的处理方式是忽略此信号。如果父进程很忙,那么可以用
signal
函数(原型为:signal(int signum, void(*handler)(int))
)为 SIGCHLD 安装 handler ,可以在 handler 中调用 wait 回收。
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
void handler(int sig) {
if (sig == SIGCHLD) {
int status;
waitpid(-1, &status, WNOHANG);
}
}
int main() {
int status;
pid_t pid;
pid = fork();
if (pid < 0) {
printf("fork failed\n");
} else if (pid == 0) {
printf("this is son process with PID=%d\n", getpid());
printf("son process is over\n");
} else {
signal(SIGCHLD, handler);
printf("this is father process with PID=%d\n", getpid());
usleep(100);
// waitpid(pid, &status, 0);
printf("father process is over\n");
}
return 0;
}
编译运行:
jincheng@jincheng-PC:~/Desktop$ gcc -o test test.c
jincheng@jincheng-PC:~/Desktop$ ./test
this is father process with PID=5819
this is son process with PID=5820
son process is over
father process is over
查看是否存在僵尸进程:
jincheng@jincheng-PC:~/Desktop$ top
top - 08:47:09 up 45 min, 1 user, load average: 0.00, 0.03, 0.07
Tasks: 151 total, 1 running, 110 sleeping, 0 stopped, 0 zombie
- 如果父进程不关心子进程什么时候结束,那么可以用
signal(SIGCHLD,SIG_IGN)
通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号。
/* test.c */
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
/*
void handler(int sig) {
if (sig == SIGCHLD) {
int status;
waitpid(-1, &status, WNOHANG);
}
}
*/
int main() {
int status;
pid_t pid;
pid = fork();
if (pid < 0) {
printf("fork failed\n");
} else if (pid == 0) {
printf("this is son process with PID=%d\n", getpid());
printf("son process is over\n");
} else {
//signal(SIGCHLD, handler);
signal(SIGCHLD,SIG_IGN); // 通知内核,父进程对子进程忽略
printf("this is father process with PID=%d\n", getpid());
usleep(100);
// waitpid(pid, &status, 0);
printf("father process is over\n");
}
return 0;
}
/*
编译运行:
jincheng@jincheng-PC:~/Desktop$ gcc -o test test.c
jincheng@jincheng-PC:~/Desktop$ ./test
this is father process with PID=6384
this is son process with PID=6385
son process is over
father process is over
查看是否存在僵尸进程:
jincheng@jincheng-PC:~/Desktop$ top
top - 09:16:17 up 1:14, 1 user, load average: 0.16, 0.07, 0.06
Tasks: 155 total, 1 running, 114 sleeping, 0 stopped, 0 zombie
*/
- 还有一些技巧,就是 fork 两次,父进程 fork 一个子进程,然后继续工作,子进程fork 一个孙进程后退出,那么孙进程被 init 接管,孙进程结束后,init 会回收。不过子进程的回收还要自己做。
点击下方图片关注我,或微信搜索**“编程笔记本”**,获取更多信息。