1. 进程等待的必要性
- 之前讲过,子进程退出,父进程如果不管不顾,就可能造成'僵尸进程'的问题,进而造成内存泄漏。
- 另外,进程一旦变成僵尸状态,那就刀枪不入,"杀人不眨眼"的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
- 最后,父进程派给子进程的任务完成的如何,我们需要知道。如:子进程运行完成,结果对还是不对,或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
2. 进程等待的方法
2.1 wait方法

wait里面的参数在waitpid里面细说,我们更多的使用的都是waitpid,wai等待的是任意一个子进程。
cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include <sys/types.h>
5 #include <sys/wait.h>
6 int main()
7 {
8 for(int i=0;i<3;i++)
9 {
10 pid_t id=fork();
11 if(id<0)
12 {
13 perror("fork");
14 return 1;
15 }
16 else if(id==0)
17 {
18 int cnt=5;
19 while(cnt)
20 {
21 printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
22 cnt--;
23 sleep(1);
24 exit(0);//结束后退出,不执行其他函数
25 }
26 }
27 }
28 int time=10;
29 while(time)
30 {
31 printf("I am father,pid:%d,ppid:%d\n",getpid(),getppid());
32 sleep(1);
33 time--;
34 }
35 for(int i=0;i<3;i++)
36 {
37 pid_t rid=wait(NULL);//父进程等待子进程结束,回收
38 printf("子进程被回收,pid为:%d\n",rid);
39 }
40 return 0;
41 }
cpp
while :; do ps ajx|head -1&&ps ajx|grep wait|grep -v grep ; sleep 1;done
输入上面的代码,我们来监控父进程和子进程的状态。

我们发现3个创建的子进程的僵尸状态消失了,并且退出。
ps:wait等待的任意的子进程。
2.2 waitpid

如果我们设置waitpid(-1,NULL,0)这样的参数的话就相当于和wait是一个效果的,都是对任意一个进程的等待。
2.2.1 status参数
wait和waitpid,都有一个status参数,该参数是一个输出型参数,我们设置int类型的变量,操作系统会把返回状态填充到变量里面。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位)


cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include <sys/types.h>
5 #include <sys/wait.h>
6 int main()
7 {
8 int num[3];
9 for(int i=0;i<3;i++)
10 {
11 pid_t id=fork();
12 if(id<0)
13 {
14 perror("fork");
15 return 1;
16 }
17 else if(id==0)
18 {
19 int cnt=5;
20 while(cnt)
21 {
22 printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
23 cnt--;
24 sleep(10);
25 exit(0);//结束后退出,不执行其他函数
26 }
27 }
28 else//父进程
29 {
30 num[i]=id;//存储子进程的pid
31 }
32 }
33 int time=10;
34 while(time)
35 {
36 printf("I am father,pid:%d,ppid:%d\n",getpid(),getppid());
37 sleep(1);
38 time--;
39 }
40
41 for(int i=0;i<3;i++)
42 {
43 int statu=0;
44 // pid_t rid=wait(NULL);//父进程等待子进程结束,回收
45 pid_t rid=waitpid(num[i],&statu,0);
46 printf("%d,exit sig:%d,exitcode:%d\n",num[i],statu&0x7F,(statu>>8)&0xFF);
47 }
48 return 0;
49 }
我们设置一个数组来保存子进程的pid,方便后面waitpid回收固定的子进程。
我们可以使用对位图的&操作来获取到子进程的退出状态,每个进程中都有保存自己的退出码和导致进程终止的退出信号,父进程也就是从子进程的PCB上面去获取。
同时我们还可以通过系统给的库函数来获取。
**WIFEXITED(status):**查看进程是否正常退出,为真表示正常退出,返回1,没有收到异常信号。
WEXITSTATUS(status): 如果WIFEXITED(status)非零,提取子进程退出码。
cpp
41 for(int i=0;i<3;i++)
42 {
43 int statu=0;
44 // pid_t rid=wait(NULL);//父进程等待子进程结束,回收
45 pid_t rid=waitpid(num[i],&statu,0);
46 // printf("%d,exit sig:%d,exitcode:%d\n",num[i],statu&0x7F,(statu>>8)&0xFF);
47 if(WIFEXITED(statu))
48 {
49 printf("child wait success,child exit code:%d\n",WEXITSTATUS(statu));
50 }
51 else
52 {
53 printf("child wait fail");
54 }
55
56 }
测试一下:
如果我们在运行过程中,发送9号信号来杀死一个子进程。


可以明显发现一个子进程等待失败了。
2.3 非阻塞等待
上面我们把waitpid的第三个参数都设为了0,也就是让父进程结果后,阻塞在那边去等待子进程结束,然后把它回收。
等待的时候也可以让父进程做一些自己的事情,设置为非阻塞轮询,也就是父进程在调用waitpid等待子进程时,发现子进程还没有结束,就可以让父进程去做别的事情,下次再过来询问子进程好了吗?没有就再去做自己的事情,当有一次子进程结束了和子进程一起退出。
改造一下代码:
cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include <sys/types.h>
5 #include <sys/wait.h>
6 int main()
7 {
8 int num[3];
9 for(int i=0;i<3;i++)
10 {
11 pid_t id=fork();
12 if(id<0)
13 {
14 perror("fork");
15 return 1;
16 }
17 else if(id==0)
18 {
19 int cnt=5;
20 while(cnt)
21 {
22 printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
23 cnt--;
24 sleep(10);
25 exit(0);//结束后退出,不执行其他函数
26 }
27 }
28 else//父进程
29 {
30 num[i]=id;//存储子进程的pid
31 }
32 }
33 int time=2;
34 while(time)
35 {
36 printf("I am father,pid:%d,ppid:%d\n",getpid(),getppid());
37 sleep(1);
38 time--;
39 }
40
41 for(int i=0;i<6;i++)//前3次循环中子进程没有结束,父进程做自己的事情,后三次循环,父进程回收子进程
42 {
43 int statu=0;
44 // pid_t rid=wait(NULL);//父进程等待子进程结束,回收
45 pid_t rid=waitpid(num[i],&statu,WNOHANG);//设置为非阻塞状态
46 if(rid<0)
47 {
48 printf("wait fail");
49 break;
50 }
51 else if(rid==0)//没有结束的子进程,可以让父进程做别的事情
52 {
53 printf("子进程还没有结束");
54 printf("我去做一些别的事情了\n");
55 sleep(3);//模拟父进程做别的事情
56 }
57 else
58 {
59 // printf("%d,exit sig:%d,exitcode:%d\n",num[i],statu&0x7F,(statu>>8)&0xFF);
60 if(WIFEXITED(statu))
61 {
62 printf("child wait success,child exit code:%d\n",WEXITSTATUS(statu));
63 }
64 else
65 {
66 printf("child quit fail");
67 }
68 }
69
70 }
71 return 0;
72 }
我们让子进程创建后10秒后再退出,把等待模型设置为非阻塞状态,把for循环设置为6次,前3次由于子进程没有结束,父进程做自己的事情,后三次父进程回收子进程。
运行结果:
