子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏,所以父进程回收子进程是必然要做的。
另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息(子进程退出的信息包括进程退出码,我进程推出时候的信号)。
进程等待的方法:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
waitpid方法:
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
下面通过一个小代码来验证:wait
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt--)
{
printf(" i am child procrss,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
}
printf("子进程退出\n");
exit(1);
}
printf("父进程开始休眠,等待子进程\n");
sleep(10);
printf("父进程开始回收\n");
pid_t rid = wait(NULL);
if(rid > 0)
{
printf("wait success,rid:%d\n",rid);
}
printf("父进程回收结束\n");
sleep(3);
return 0;
}
该代码,总共运行十三秒,前五秒父进程子进程一起运行,前五秒过后子进程退出。中间五秒父进程等待回收子进程,这时会看到子进程僵尸的状态,中间五秒后,父进程回收子进程。最后三秒,父进程休眠三秒,然后程序退出。
注意:fork之后,父进程和子进程谁先运行是不确定的,由调度器说了算。
修改一下代码,使用waitpid。
通过上文对waitpid的描述,第一个参数传id,是子进程的id。第二个参数传递一个地址,这个记录进程退出时的退出码和收到的信号,第三个参数设置为0表示阻塞等待。
通过上图我们可以看出,进程推出的退出状态时status = 256 。那么时为什么呢?
status是一个整形,有三十二个bit位,前16个bit位不用,后十六个bit位的前八位表示进程退出时的退出码,后七位表示进程退出时收到的信号。子进程退出时设置的时exit(1),故而经过转化,得到的status是256。
如果想直接拿到信号编号和进程退出码,还可以这样写。
printf("wait success! rid:%d , status:%d,exit singno:%d, exit code:%d\n",rid,status,status&0x7F,(status>>8)&0xFF);
这样显示的就比较直观了。
如果不想着麻烦的话,Linux系统同时给我们提供了两个宏让我们可以直接提取到进程的退出码。
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
用法如下:
if(WIFEXITED(status)){
printf("wait success! rid:%d , status:%d,exit code:%d\n",rid,status,WEXITSTATUS(status));}
同时waitpid的第三个参数设置为0表示阻塞调用,而设置为WNOHANG 表示非阻塞调用。
阻塞调用表示:父进程一直等待当前子进程到结束,然后读取返回的退出码以及信号。
非阻塞调用表示:父进程基于非阻塞的轮询访问子进程,访问一次子进程发现没有结果,然后就返回,不必等到子进程有结果才返回。其间父进程也可以去执行别的代码,而不是一直卡住等待子进程有结果。
注意:为什么我们不能自己定义一个变量,子进程退出修改这个变量,然后给父进程呢?
答案是不行的,进程间具有独立性。父进程创建子进程,当子进程对数据做修改时会发生写时拷贝。子进程的信息属于内核数据,父进程读取子进程属于读取内核数据,而操作系统不相信任何人,所以只能通过系统调用来读取。同时,用户定义的变量属于用户层面的,而子进程的信息属于内核数据,子进程不能直接对用户层面的信息做修改,还是要通过系统调用。
下面是一个代码演示了基于非阻塞轮询访问子进程,其间父进程也可以执行别的代码。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#define NUM 5
typedef void(*fun_t)();
fun_t task[NUM];
void printflog()
{
printf("this is log task\n");
}
void printfnet()
{
printf("this is net task\n");
}
void printfnpc()
{
printf("this is npc task\n");
}
void inittask()
{
task[0] = printflog;
task[1] = printfnet;
task[2] = printfnpc;
task[3] = NULL;
}
void excutetask()
{
for(int i = 0;task[i];i++) task[i]();
}
int main()
{
inittask();
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("i am child process,pid:%d ,ppid:%d\n",getpid(),getppid());
sleep(1);
cnt--;
}
exit(111);
}
int status = 0;
while(1)
{
pid_t rid = waitpid(id,&status,WNOHANG);
if(rid > 0){
printf("wait success! rid:%d , status:%d,exit
code:%d\n",rid,status,WEXITSTATUS(status));
break;
}
else if(rid == 0)
{
printf("father say: child is runnin,do other thing\n");
printf("##################begin######################\n\n");
excutetask();
printf("##################end######################\n");
}
else{
perror("waitpid");
break;
}
sleep(1);
}
return 0;
}