进程等待
进程等待是什么
父进程fork()出子进程之后,需要调用wait()或者waitpid()等待子进程退出。
进程等待的原因
- 子进程退出的时候进入僵尸状态,会造成内存泄漏的问题,需要通过父进程等待去释放子进程的资源
- 通过获取子进程退出的状态,得知子进程的执行结果
- 在一定程度上保证了父进程后于子进程退出
进程等待的方法
1.wait()
1.1函数原型
pid_t wait(int *status);
1.2函数参数
其中的参数status我们在接下来的waitpid中介绍,此处我们可以暂时设置他为NULL。
1.3函数返回值
函数的返回值如果 >0 则为等待子进程的pid, <0 则表示等待失败。
1.4代码实现
#include <stdio.h>
#include <unistd.h> //fork
#include <stdlib.h> //exit
#include <sys/wait.h> //wait
#include <sys/types.h>
int main()
{
pid_t id = fork();//创建子进程
if(id < 0){
//fork出错
perror("fork error\n");
exit(1);
}else if(id == 0){
//child
int time = 5;
while(time > 0)
{ //每隔一秒打印一次 五秒后子进程退出
printf("child[%d] will exit %d\n",getpid(),time);//
time--;
sleep(1);
}
exit(0);
}
//parent 先等待7秒 也就是说子进程退出后再过2秒父进程执行
sleep(7);
pid_t ret = wait(NULL); //等待子进程
if(ret < 0){
perror("wait error\n");//等待失败
exit(2);
}
//wait success
printf("father wait child[%d] success!\n",ret);
sleep(10);
return 0;
}
1.5运行结果
另外开一个脚本观察子进程运行状态:
可见在父进程等待成功之后,子进程才从僵尸状态成功释放资源退出。
2.waitpid()
2.1函数原型
pid_t waitpid(pid_t pid, int *status, int options);
2.2函数参数
pid: 有两种选择
(1) pid = -1:表示等待任意一个子进程(与wait功能相同)
(2) pid = id:此处id表示fork之后的子进程id,父进程这时候专门等待此进程
status:是一个输出型参数,有一个int空间的地址(32位),用于向指定空间中存放子进程的退出返回值
(1) 如果不关心子进程的返回值,可以设置为NULL(与wait功能相同)
(2) 高16位暂不关心,低16位中的高8位表示进程的退出码,低7位表示异常退出码,第8位coredump标志位也暂不关心
利用status判断进程是否正常退出:只需要取出status的低7位即可,为0表示正常退出,否则则为异常退出。 (status & 0x7f )即可取出低7位。
只有异常信号量为0(正常退出),那么进程的退出码才有意义想要取出退出码只需要右移8位然后和0xff相与即可 (status >> 8) & 0xff.
如果嫌相与太麻烦,那么可以用接下来的两个宏取代上面的步骤:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:有两种选择
(1) 0:表示阻塞等待。
(2) WNOHANG:表示非阻塞等待。
给大家举个简单的例子,帮助大家理解阻塞与非阻塞:
阻塞等待就像期末考试的时候考场上你没有做完卷子老师什么都不做,等你交卷子。 非阻塞等待就像平时给你卷子,你如果没有写完,老师会每隔一小时或一节课来问你写完没写完,如果你没写完,他不会一直等你,而是去收别人的卷子或者去干别的事。老师的状态就是父进程的状态,这下相信大家理解了吧。
2.3函数返回值
- 正常返回的时候waitpid返回等待到的子进程的pid;
- 如果设置了WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,返回0;
- 如果调用中出错,则返回-1;
2.4代码实现
版本1: 实现阻塞等待任意子进程
#include <stdio.h>
#include <unistd.h> //fork
#include <stdlib.h> //exit
#include <sys/wait.h> //wait
#include <sys/types.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork error\n");
exit(1);
}else if(id == 0){
//child
int time = 5;
while(time > 0)
{
printf("child[%d] will exit %d\n",getpid(),time);
time--;
sleep(1);
}
exit(0);
}
//parent
sleep(7);
pid_t ret = waitpid(-1,NULL,0); //这时waitpid和wait作用相同
if(ret < 0){
perror("wait error\n");
exit(2);
}
//wait success
printf("father wait child[%d] success!\n",ret);
sleep(10);
return 0;
}
运行结果:除了进程pid外其余全部一样
版本2:阻塞等待特定子进程并且获取子进程退出状态
#include <stdio.h>
#include <unistd.h> //fork
#include <stdlib.h> //exit
#include <sys/wait.h> //wait
#include <sys/types.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork error\n");
exit(1);
}else if(id == 0){
//child
int time = 5;
while(time > 0)
{
printf("child[%d] will exit %d\n",getpid(),time);
time--;
sleep(1);
}
exit(0);
}
//parent
sleep(7);
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(ret < 0){
perror("wait error\n");
exit(2);
}else if(ret > 0){
//等待到子进程
if((status & 0x7f) ==0 ){
//正常退出
printf("father wait child[%d] success!\n",ret);
//获取退出码
printf("child[%d] exit code: %d\n",ret,(status>>8 & 0xff));
}else{
//异常退出
printf("进程异常退出,获得一个信号\n");
}
}
sleep(10);
return 0;
}
运行结果:子进程中exit设置的为0所以退出码为0
脚本显示:
注意:运行结果显示可能每次都差不多,大家可以忽略结果图,理解代码变化即可。
另外,因为代码中每次父进程执行完所有代码后会sleep10秒 脚本没有截全,为了避免大家误解在此解释一下。 子进程退出后状态变化如下所示:
最终版本 非阻塞等待子进程并且获取子进程退出码
#include <stdio.h>
#include <unistd.h> //fork
#include <stdlib.h> //exit
#include <sys/wait.h> //wait
#include <sys/types.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork error\n");
exit(1);
}else if(id == 0){
//child
int time = 5;
while(time > 0)
{
printf("child[%d] will exit %d\n",getpid(),time);
time--;
sleep(1);
}
exit(6);
}
//parent
int status = 0;
while(1){
pid_t ret = waitpid(id,&status,WNOHANG);
if(ret == 0){
//设置了WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,返回0
printf("father can do other thing...\n");
}else if(ret > 0 && WIFEXITED(status)){
//WIFEXITED 若为正常终止子进程返回的状态,则为真
printf("fahter wait child[%d] success, status exit code: %d\n", ret,WEXITSTATUS(status));
break;
}else if(ret > 0){
printf("进程异常退出,退出信号为:%d\n",(status&0x7f));
}
else if(ret < 0){
perror("wait error\n");
exit(2);
}
sleep(2);
}
sleep(3);
return 0;
}
运行结果:
小插曲:我们一直写的代码是正常退出的,现在我们实现个异常退出的情况
手动利用kill结束掉子进程 ,此时走(ret > 0)的循环 打印一条消息获取退出信号,但是没有break,所以会再进入循环,但此时子进程已经被kill掉 ,所以父进程等待失败 (ret < 0) 打印 wait error 然后break,父进程结束。
关于进程等待常见的情况就介绍完了。