什么是进程等待?
通过系统用wait/waitpid,来进行对子进程进行状态检测与回收的功能!
为什么要有进程等待?
- '僵尸进程'无法被杀死,只能通过进程等待来回收, 进而解决内存泄漏问题。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息----- 子进程运行完成,结果对还是不对, 或者是否正常退出。(父进程可以关心也可以不关心,这条不是必选)
补充(关于父子进程的理解):
其实我们到目前为止,不管是在命令行上所启动的进程 ,还是我们自己在代码层面 上创建出来的进程,它都叫做子进程。
Linux整个的进程结构本质就是一些父子关系的多叉树 结构。那么我们命令行启动的所有的进程或者任务,最终其实都是父进程bash的子进程 。想象一下,如果我们自己在fork的时候,虽然出现了父进程,但这个父进程它也是别人的子进程。
系统怎么使用进程等待的?
代码验证:
cpp
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid id=fork();
if(id<0)
{
perror("fork");
return 1;
}
else if(id==0)
{
int cnt=5;
while(cnt--)
{
printf("I am child pid:%d,ppid:%d,cnt:%d \n",getpid(),getppid(),cnt);
sleep(1);
}
exit(0);
}
else
{
//parent
int cnt=10;
while(cnt--)
{
printf("I am child pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
sleep(1);
}
pid_t ret=wait(NULL);
if(ret==id)
{
printf("wait success ,ret:%d\n")
}
sleep(5);
}
return 0;
}
关于wait接口:
fork给我们对应的父子进程都会返回id值,子进程返回pid值等于0,
父进程的返回的pid其实这个就是子进程的pid。
wait:它就是用来让当前对应的父进程来进行等待子进程。
那么我们等待成功的时候ret是等待的子进程的pid。
换句话说,为什么要给父进程返回子进程的PID呢?
就是要用来判断我们当前父进程等待的时候等待的是哪个子进程,ret这个值返回值小于0小于0说明等待失败。
代码运行逻辑:
就是很显然这个程序一旦跑起来了:
那么对应的进程呢先创建父子进程
在前5秒的时候,父子都在运行,
5秒之后 ,子进程先退了之后变成僵尸状态等待回收 。
,但是我们的父进程依旧没有进行wait,因为它还在执行前面的while。
再过5秒之后 ,我们父进程回收子进程僵尸状态,最后父进程也结束;
所以进程等待是很有必要的!!!
如果我们有多个子进程需要回收,就需要创建多个wait来对其进行回收;
如果子进程不退出,父进程默认在wait的时候,调用这个系统调用的时候,也就不返回,默认叫做阻塞状态!
关于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。
这里的waitpid参数这么写和wait的达到的效果是差不多的
关于status参数:
它是一个int*,也就是一个指针。但实际上呢,我们给它传参的时候确实是要传一个指针的。
只不过这里是为了通过wait或waitpid拿到指定进程或者任意一个进程它所对应的退出结果的。
第一个从语法角度,这个整数,它是一个输出型参数。
关于输出型参数:
关于输出型参数是c,c++语法的基础,就是期望通过对应的指针把函数内部所对应的数据带出来。
因为大家都知道我们传参一般都是要给函数传参的。
但有些形参最后我们可以把实际化之后变成指针。
然后通过status把函数内部的资源带出去,比如说对空间,比如说我们对应的返回值。
所以这种把值带出来的这种参数类型 ,我们叫做输出型参数
我的传参不是为了交给你对应的,这个函数什么,而是想让你函数交给我什么,这叫做输出型参数来。
第二,int看起来是整数,但是它这个int是被当做几部分使用的不要把这个int当做一个整数来看。
因为一个int在我们现有的这个平台当中
一个int类型占32比特位,那么也就意味着:我们其实是可以把我们所对应的这个int类型呢,可以看作成比如8个了、或者16个了。
所以把它分批可以表达不同的含义。
所以呢,这个int它是被当做几部分来使用 的。
所以对我们来讲,我们想获取对应的结果呢。
在这里int就得定一个status变量这个变量名字可以随便起,只要它是个整数就行了。
一旦这个子定程它退出了。
我们会把它的退出信息拷贝到这个status指针所指向的变量当中。
所以当我们等待成功的时候,我们此时就能够得到这个子进程的对应退出信息。
其中这个status,它高16位不考虑,我们只考虑它的低16位
严格意义上讲是第8位,就是它的最低的。
那8个比特位用来表示的是我们这个当前进程是否出异常的问题
我们一个进程在异常终止时,一个子进程或者一个进程的异常时,本质是该进程收到信号了。
所以我们只要判断终止信号,这里的低7个比特位是否为0就能证明你的代码是否跑完 。
可是跑完就对吗?
不一定。
我们可以通过这里对应的次第8位: 退出状态来得知子进程退出结果,所以就叫做退出状态。
所以退出状态为0,表示代码跑完结果正确,那么信号为0。
退出状态为1,代码跑完结果不正确,不正确的原因每一个不同的数字可以表示出错的不同原因。
父进程要拿子进程的状态数据或者任意数据,为什么必须要wait等系统调用呢?
首先父子进程之间是具有独立性的。
所以你只能通过我们官方渠道----waitpid来指定进程获取它的特殊结果,并且父进程只能wait它对应的子进程。