文章目录
1. 进程等待是什么
我们知道一般我们在父进程fork出一个子进程,我们是希望子进程完成某些功能,也就是帮助父进程完成某些任务的;所以我们父进程就需要知道子进程完成的状态如何,是成功还是失败;
所以我们就需要父进程通过wait 或者 waitpid 函数等在子进程退出;
2. 为什么需要父进程等待子进程退出
- 父进程等待子进程退出,是因为父进程需要子进程退出的信息,和完成功能的状态如何;
- 可以保证时序问题:子进程先退出,父进程再退出;
- 可以预防子进程成为僵尸进程,防止内存泄漏的问题;而这我们需要父进程wait等待子进程退出之后,释放它的僵尸资源,也就子进程的PCB;
- 并且我们需要知道,一旦进程成为僵尸状态,即使你使用
kill -9
也杀不死这个僵尸进程滴,只能通过父进程等待wait回收它;
3. 进程等待的方式
3.1 wait 函数
wait函数的作用是父进程调用,等待子进程退出,回收子进程的资源;
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
该测试代码:测试wait函数返回值,测试wait回收僵尸进程;
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
if(fork() == 0){
//child process
printf("i am a child pid=%d\n",getpid());
exit(0); //让子进程退出
}
//parent process执行,这里不会执行子进程了,因为子进程被我退出了
sleep(2); //休息2s,为的是观察监控消息,是否子进程成为僵尸进程
printf("wait函数开始执行\n");
pid_t ret = wait(NULL);
if(ret ==-1){
perror("wait error\n");
}
//wait返回成功
printf("wait返回的是子进程的ret=%d执行结束,注意观察监控窗口是否>僵尸进程被回收\n",ret);
sleep(2); //不让父进程那么快退出,观察窗口僵尸进程是否被回收
return 0;
}
监视 myproc
进程的脚本程序;
while :; do ps ajx | head -1 && ps ajx | grep myproc | grep -v grep;sleep 1; echo "#############";done
3.2 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:
参数为0:也就是阻塞版本的等待,也就是说该waitpid在子进程没有退出情况下就不会返回,就和wait的使用一模一样,因为wait的使用就是阻塞版本的等待方式;
参数为WNOHANG: 这是一个宏,表示调用wait为非阻塞的版本,非阻塞也就以为执行带waitpid函数会立即返回;
而设置这个参数:返回情况有以下几种:
- 若pid指定的子进程没有结束,则waitpid()函数返回0,父进程不予以等待;
- 若正常结束,则返回该子进程的ID;
- 若等待失败,即返回小于0;
第一层理解:
对于 waitpid 函数就是 wait函数的增强版;
waitpid 函数 的使用方式 waitpid(-1,NULL,0) 等价 wait 函数的使用 wait(NULL) 两者这样使用一样的;
第二层理解:
- 对于status参数,其实是一个输出型参数,也就是父进程调用该waitpid时候,可以传入一个 地址给 status;待该waitpid执行结束返回时候,会得到该staus的值;
- status的值表示子进程的退出码的信息,也就是父进程为了得到子进程的退出信息,就是可以通过设置一个参数传入给status,获得子进程的信息;
- status的退出信息:也就是子进程退出的信息,而进程退出只有三种状态:正常退出执行代码结果正确,异常退出,正常退出了但是执行的结果不正确;异常退出的进程:本质是因为收到了某种信号,才会异常退出,而对于正常退出的进程,才有退出码而言说法;不管是信号还是退出码,都是子进程需要返回给父进程中stauts参数的;
- 其次这个 stauts,父进程获得子进程的status;不可以简简单单的认为 stauts就是一个整形int,我们要把它为一个
位图;对于32位的int类型来说:
我们status是在每一个位上设置它的信息来使用的;高16位不使用,而低16位使用来表示具体信息;
3.2.1获得子进程的status信息
也就是说:对于子进程退出的状态信息:在8-16位的表示退出码的信息,低7位表示终止信号信息;而第8位单独一个表示core dump 状态,我们暂时不关系这个信息;
那么我们是如何获得该子进程的退出的信息的呢?
我们可以通过
(stauts >> 8 )& 0xFF;获得子进程的退出码;
stauts & 0x7F; 获得子进程的终止信号;
代码验证一下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid == 0){
//child
printf("i am a process my pid = %d\n",getpid());
exit(11); //退出子进程,我们在父进程调用waitpid来获得子进程的退出码信息
}
//parent process
int status; //该变量是父进程的变量,为的是在父进程获得子进程的退出状态的信息
pid_t ret = waitpid(pid,&status,0);//传入status的地址,获得子进程的退出的信息
if(ret == -1){
perror("wait failed\n");
}
//waitpid 等待成功
//打印子进程的stauts退出码信息和终止信号信息,这是在父进获取到子进程的信息
printf("waitpid 返回的stauts 的退出码信息:%d,终止信号的信息:%d\n",(status>>8)&0xFF,status&0x7F);
return 0;
}
这里我并没演示退出时候异常退出的情况,也就是我没有演示发送信号给子进程会有什么样的状态:
其实就是在另一个窗口给子该进程发送一个信号值,当进程退出时候,这个信号就会被返回到父进程的stauts中,这样就能获取了;
当然异常的情况还有内存越界,什么段错误,什么除0操作,一旦程序出现这些状态,也可以在父进程waitpid中的参数status中获取得到,我就不演示了;感兴趣可以试一试;
我们获得子进程的退出码,其实可以不用通过:位操作的(status >> 8 )& 0xFF
获取,
在我们的C程序中提供一个宏:
WIFEXITED(status)
: 若为正常终止子进程返回的状态,则为真;
那么我们就可以通过if判断该条件是否真假,获得子进程的退出码:
获得的方式为这个宏WEXITSTATUS(status)
: 若WIFEXITED非零,提取子进程退出码。
测试子进程正常退出,获取它的状态码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid == 0){
//child
printf("i am a process my pid = %d\n",getpid());
exit(11); //退出子进程,我们在父进程调用waitpid来获得子进程的退出码信息
}
//parent process
int status; //该变量是父进程的变量,为的是在父进程获得子进程的退出状态的信息
pid_t ret = waitpid(pid,&status,0);//传入status的地址,获得子进程的退出的信息
if(ret == -1){
perror("wait failed\n");
}
//waitpid 等待成功
if(WIFEXITED(status)){
printf("子进程pid = %d 正常退出,父进程获得子进程的退出码为:%d\n",pid,WEXITSTATUS(status));
}
else{
printf("WIFEXITED为假,子进程异常退出,非正常退出\n");
}
return 0;
}
测试代码的结果:
假如子进程异常退出,那么就会执行else分支语句,不会获得退出码;
3.2.2 理解下waitpid内部是如何返回status的
我们知道:当我们创建进程时候,在内核是有该进程的PCB的,而PCB里面存放着我们进程退出的信息;
也就是有退出码和终止信号,在用户层父进程调用waitpid函数时候,该函数进入内核会给把我们的子进程的PCB退出码和终止信号信息给status赋值;
也就是大概类似这样的操作:
3.2.3 waitpid 的options参数的理解
该参数设置:一般设置为WNOHANG:表示为父进程是以非阻塞的方式等待子进程;
但是非阻塞方式等待子进程退出就有几种情况:
> 1.子进程没有退出,但是父进程调用的waitpid返回了0,这也表示等待子进程成功,只不过子进程没有退出,此时表示需要继续做父进程的事情;
2. 子进程退出了,f父进程调用waitpid函数返回子进程的PID, 也就是等待成功了,这时候我们在父进程可以拿到子进程的退出状态信息;
3. waitpid等待子进程退出失败,waitpid就会返回小于0的值,此时就可以做一些输出错误信息给用户;
阻塞本质就是:调用该函数的父进程由在运行队列被放入到了等待队列中等待,同时修改进程状态为S;
waitpid返回的本质也就是:将该父进程从等待队列拿到运行队列中执行;
测试options:WNODHANG
非阻塞等待子进程;
一般而言我们会使用一种叫做非阻塞轮回检测技术来检测子进程的退出状态,也就是说:我希望子进程退出能够被我父进程检测到,同时我又不希望我父进程处于阻塞等待,也就是父进程不希望自己什么事都不可以做,只等子进程退出返回;
如何做到呢?
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid == 0){
//child
int count = 10;
while(count){
printf("i am a process my pid = %d\n",getpid());
count --;
sleep(1);
}
exit(11); //退出子进程,我们在父进程调用waitpid来获得子进程的退出码信息
}
//parent process
int status; //该变量是父进程的变量,为的是在父进程获得子进程的退出状态的信息
while(1){ //这个循环就是继续轮回检测的非阻塞版本的设计,假如子进程没退出,我们一直死循环检测知道直到它退出
pid_t ret = waitpid(pid,&status,WNOHANG);//WNOHANG:表示父进程非阻塞方式等待子进程退出
if(ret==0){
//ret == 0 表示waitpid等待成功,但是子进程还没有退出,waitpid返回0回到父进程的代码执行
//做父进程的事情;
printf("我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事\n");
}
else if(ret > 0){
//waitpid 等待成功,子进程退出,父进程就可以获取子进程的信息
printf("waitpid 返回的stauts 的退出码信息:%d,终止信号的信息:%d\n",(status>>8)&0xFF,status&0x7F);
break;
}
else{
printf("waitpid is failed\n");
break;
}
sleep(1); //让父进程每隔一秒去检测
}
return 0;
}