进程等待的必要性
- 子进程退出,父进程如果不管不顾,就会造成“僵尸进程”,从而造成内存泄漏等问题。
- 进程一旦进入僵尸进程状态,就刀枪不入,kill -9 也无法杀死。
- 父进程给子进程派出的任务完成情况,不进行进程等待就无法获取。如:子进程运行完成,结果是否正确;程序是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。
进程等待方法
wait
1、函数使用
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int * status);
2、返回值:
wait成功:返回被等待进程的 pid
wait失败:返回 -1
3、参数:
输出型参数,获取子进程的退出状态,不关心返回值则可以设置成NULL
4、wait 是阻塞式的函数,子进程结束才返回。
5、wait 的个数必须和子进程个数一致,否则如果wait次数少于子进程数,没有被等待的子进程就会进入僵尸状态;wait次数大于子进程数,除了调用出错,没别的影响。
6、进程阻塞等待代码实现
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
printf("%s fork error\n",__FUNCTION__);
return 1;
}
else if(pid == 0)
{
//child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(257);
}
else
{
//father
int status = 0;
pid_t ret = waitpid(-1,&status,0);//阻塞式等待,等待5秒
printf("this is test for wait !\n");
if(WIFEXITED(status) && ret == pid)
printf("wait child 5s success, child return code is : %d.\n",WEXITSTATUS(status));
else
{
printf("wait child failed ,return.\n");
return 1;
}
}
return 0;
}
运行结果:
[LXY@localhost wait]$ ./wait
//立即执行并打印
child is run, pid is : 20102
//父进程阻塞等待5秒之后,等待到子进程的退出,然后执行并打印结果
this is test for wait !
wait child 5s success, child return code is : 1.
解释:子进程不退出,父进程就一直阻塞在那,等待子进程退出后才执行后面代码。
waitpid
1、函数使用
#include <sys/types.h>
#include <wait.h>
pid_t waited(pid_t pid, int *status, int options);
2、返回值
- 正常返回的时候 waitpid 返回收集到的子进程的进程ID;
- 如果第三个参数设置了选项 WNOHANG,而且调用中 waitpid 发现没有已经退出的子进程可以收集,则返回 0 ;
- 如果调用中出错,则返回 -1 ,这时errno 会被设置成相应的值来指示错误所在。
3、参数
(1)pid
pid = -1,等待任意一个子进程。与wait等效。
pid > 0, 这时pid是一个子进程的进程ID,等待其进程ID与pid相等的子进程。
(2)status
WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否正常退出)
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
(3)options
WNOHANG:若pid指定的进程没有结束,则waitpid()函数返回0,不予以等待;若正常结束,则返回该子进程的ID。
4、waitpid是非阻塞轮询式的等待
(1)优点:能够更灵活的控制代码,充分利用等待时间做其他事情。
(2)缺点:代码更复杂。
5、进程非阻塞等待代码实现:
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
printf("%s fork error\n",__FUNCTION__);
return 1;
}
else if(pid == 0)
{
//child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(1);
}
else
{
//father
int status = 0;
pid_t ret = 0;
do{
ret = waitpid(-1,&status,WNOHANG);//非阻塞式等待
if(ret == 0)
printf("child is running !\n");
sleep(1);
}while(ret == 0);
if(WIFEXITED(status) && ret == pid)
printf("wait child 5s success, child return code is : %d.\n",WEXITSTATUS(status));
else
{
printf("wait child failed ,return.\n");
return 1;
}
}
return 0;
}
运行结果:
[LXY@localhost wait]$ ./waitpid
//立即执行并打印
child is running !
child is run, pid is : 20704
//每隔一秒执行一次
child is running !
child is running !
child is running !
child is running !
//五秒等待结束,打印最终结果
wait child 5s success, child return code is : 1.
解释:这个代码完美解释了waitpid的轮询式非阻塞等待进程过程。就相当于它每隔一秒来查看一次子进程的退出情况,若查看时子进程退出了,就返回结果,若还没退出就去做其他事,过一秒再来查看。(这个查看间隔时间是自己设置的)
进程等待过程
- 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
- 如果在任意时间调用wait/waitpid,子进程存在且还在正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立即出错返回。
获取子进程的status
- wait/waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果status设置成NULL,则表示不关心子进程的退出状态信息。
- 如果不是NULL,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- status不能当作整型来看待,可以作为位图来研究(下图研究了status低16位比特位)
如图:
如何通过代码获取子进程的status?
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main()
{
pid_t pid;
if((pid = fork()) == -1)
perror("fork"),exit(1);
if(pid == 0)
{
sleep(20);
exit(10);
}
else
{
int st;
int ret = wait(&st);
if(ret > 0 && (st & 0X7F) == 0)
{
//正常退出
printf("child exit code: %d\n",(st >> 8) & 0XFF);
}
else if(ret > 0)
{
//异常退出
printf("sig code : %d\n",st & 0X7F);
}
}
}
运行结果:
//正常退出结果
[LXY@localhost wait]$ ./status
child exit code: 10
//新开一个终端,kill指令杀死子进程结果
[LXY@localhost wait]$ ./status
sig code : 15