在前面的文章中我们讨论了如何通过 fork() 函数创建子进程,创建后的子进程如果优先于父进程退出,子进程的虚拟内存空间就消失了,但是进程控制块PCB并没有消失,这里面包含了这个子进程的退出状态,需要由父进程来进行回收。在父进程回收之前,这个进程被称为僵尸进程(僵死进程),任何一个子进程都会经过这段僵尸进程的阶段,最后由父进程来回收。

子进程退出有两种情况,一种是程序正常退出了,比如exit(1)或者main函数返回等。而另外一种则是非正常退出,一般情况下是收到了某种信号,比如“kill -9”。正常退出的情况下,我们要获取进程退出的代码,而非正常退出的状态下我们要获取到底进程因为哪中信号而终止了。 我们首先来看一个最简单的 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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
pid_t pid, wpid;

pid = fork();

if (pid == -1)
{
perror("fork error");
exit(1);
}
else if (pid == 0)
{
printf("I'm child, PID = %d\n", getpid());
// 子进程等待5秒
sleep(5);
}
else if (pid > 0)
{
// 父进程调用wait函数处于阻塞状态,直到子进程结束wait才会返回
wpid = wait(NULL);
if (wpid == -1)
{
perror("wait error");
}
printf("I'm parent, I catched child process, PID = %d\n", wpid);
}
return 0;
}

上面代码是一个最简单的 wait() 的应用了,这样的 wait() 只起到了回收子进程 PCB 的作用,而并没有对子进程的退出消息做任何的接收或处理。下面的代码演示了如何对子进程的退出情况做出响应。

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
pid_t pid;
pid = fork();
if (pid == -1)
{
perror("fork error");
exit(1);
}
else if (pid == 0)
{
printf("I'm child, PID = %d\n", getpid());
sleep(1);
}
else if (pid > 0)
{
int status;
int wpid;
wpid = wait(&status);
// 子进程正常退出判断,如果返回真则证明子进程是正常退出的
if (WIFEXITED(status))
{
// 打印已经退出的子进程的 pid
printf("I'm parent, The child process %d exit normally\n", wpid);
// 打印子进程程序退出后的返回值
printf("return value : %d\n", WEXITSTATUS(status));
}
// 如果子进程是非正常退出,那么一定是接收到某信号,下面就是判断是否是接收到了信号
else if (WIFSIGNALED(status))
{
// 获取进程是接收到了什么信号导致非正常退出的
printf("Child terminated abnormally, signal %d\n", WTERMSIG(status));
}
}
return 0;
}

从上面的例子中可以看出,WIFEXITED是用来判断子进程的退出状态的,如果返回真则证明子进程是正常退出的,随后使用了WEXITSTATUS获取了退出代码。而WIFSIGNALED则是判断子进程是否是接收到了某个信号,如果是收到了某个信号证明进程是非正常退出的,那么使用WTERMSIG获取收到的信号。可通过 kill -l 查看信号编号的对应内容。