孤儿进程与僵尸进程

本文主要内容包括:

  • 产生孤儿进程、僵尸进程的条件;
  • 通过程序来产生孤儿进程与僵尸进程。

孤儿进程与僵尸进程的产生条件

在 UNIX/Linux 中,正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。于是就产生了孤儿进程和僵尸进程。

  • 孤儿进程:是指一个父进程退出后,而它的一个或多个子进程还在运行,那么那些子进程将会成为孤儿进程。孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。
  • 僵尸进程:是指一个进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵尸进程。当一个进程完成它的工作终止之后,它的父进程需要调用 wait() 或 waitpid() 系统调用取得子进程的终止状态。

可以这样理解孤儿进程和僵尸进程的区别:孤儿进程是父进程已退出,而子进程未退出;僵尸进程是父进程未退出,而子进程已退出。他们的共同点:都是子进程处于这两种状态。


通过程序产生孤儿进程与僵尸进程

下面通过程序来具体展示如何产生孤儿进程与僵尸进程,并且说明如何在 Linux 系统中检测这些处于非正常状态的进程。

产生一个孤儿进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <unistd.h>

using namespace std;

int main(){
pid_t pid;
pid = fork();
if (pid > 0){ /* 父进程 */
cout << "Parent, PID: " << getpid() << " Child PID: " << pid << endl;
}
else if (pid == 0){ /* 子进程 */
cout << "Child, PID: " << getpid() << " Parent PID: " << getppid() << endl;
sleep(10);
}
return 0;
}

输出为:

1
2
3
# ./a.out
Parent, PID: 16911 Child PID: 16912
Child, PID: 16912 Parent PID: 1

可以看到,子进程中输出的父进程ID是 1,而不是 16911。这就是因为在子进程睡眠的时间内,父进程运行结束并退出了,子进程称为了一个孤儿进程,并且被 init 进程收养,所以子进程的父进程ID就是 init 进程的进程ID了。

或者比较快地(睡眠时间结束前)在另一个终端上面使用 ps 命令可以显示出 PID 为 16912 的进程的 PPID 是 1。

1
2
3
$ ps -ef |egrep '(a.out|PID)' |grep -v grep
UID PID PPID C STIME TTY TIME CMD
chl 16912 1 0 16:17 pts/18 00:00:00 /tmp/a.out

以上用了支持扩展正则表达式的 egrep 命令来过滤输出,以同时输出标题行。并且使用 grep -v 来反向选择了 grep,使得不输出包含 grep 的行。


产生一个僵尸进程:

还是和上面类似的进程,不过是需要使父进程进入睡眠:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <unistd.h>

using namespace std;

int main(){
pid_t pid;
pid = fork();
if (pid > 0){
cout << "Parent, PID: " << getpid() << " Child PID: " << pid << endl;
sleep(10);
}
else if (pid == 0){
cout << "Child, PID: " << getpid() << " Parent PID: " << getppid() << endl;
}
return 0;
}

输出为:

1
2
Parent, PID: 17135 Child PID: 17136
Child, PID: 17136 Parent PID: 17135

以上程序中,子进程会比父进程先运行完并退出,但是父进程并没有采用其他的手段来获取子进程的状态消息并回收子进程的进程描述符,所以子进程变成了一个僵尸进程。

既可以通过 top 命令来检测当前系统的运行环境中存在僵尸进程,但是无法查看具体的信息。也可以通过 ps 命令来查看状态为 Z 的进程即为僵尸进程:

1
2
3
$ ps aux |grep Z |grep -v grep
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
chl 17136 0.0 0.0 0 0 pts/18 Z+ 16:36 0:00 [a.out] <defunct>

可以看到 PID 为 17136 的进程的状态 STAT 为 Z+,由此就创建了一个僵尸进程。但是在父进程结束睡眠并退出之后,如果再次使用上述的 ps 命令查看的话,会发现该僵尸进程消失了,这是因为:父进程退出后,这个僵尸进程就成为孤儿进程,过继给了 init 进程,而 init 进程会周期性地调用 wait 系统调用来清除各个僵尸的子进程。