一、概念
进程等待,就是通过wait/waitpid的方式,让父进程(一般)对子进程进行资源回收的等待过程。
二、原因
(1)
当一个进程在退出的时候,如果不回收,就会变成僵尸状态,它让父进程等待之后,将僵尸状态进行回收。
僵尸就是进程或者子进程对应的代码和数据已经被释放,但是它的PCB以及它进程退出的相关数据依旧被维护,需要让父进程得知它的退出结果。
总结:
解决子进程僵尸问题带来的内存泄漏问题(是必须的)
(2)
父进程创建子进程,是为了让子进程帮助父进程完成父进程分配给子进程的任务。
所以,父进程需要知道子进程任务的完成情况。
总结:
需要通过进程等待的方式,获取子进程退出的信息。(不是必须的,但是系统需要提供这样的基础功能(可以不用,但不能没有))
三、如何实现进程等待
1、wait
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
演示:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
void Worker()
{
int cnt = 5;
while(cnt)
{
printf("这是子进程,pid:%d, ppid:%d, cnt: %d\n",getpid(), getppid(), cnt);
cnt--;
sleep(1);
}
}
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
Worker();
exit(0);
}
else
{
sleep(10);
pid_t rid = wait(NULL);
if(rid == id)
{
printf("wait sucess, pid:%d\n",getpid());
}
}
return 0;
}
运行结果:
监视:
可以看出子进程运行完毕之后,在父进程sleep的时间内,子进程是Z状态(僵尸状态)
随后父进程wait,子进程被回收,不再是僵尸状态。
思考:
如果将代码中的sleep(10)删除,即表示父进程一开始直接执行到wait,那么情况又是什么样的呢?
运行结果:
监视:
总结:
进程等待能够回收子进程僵尸状态,Z->X
如果子进程没有退出,父进程必须在wait上进行阻塞等待,直到子进程僵尸,wait自动回收,返回。
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:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID
演示:
(1)效果与wait相同
waitpid(id,NULL,0)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
void Worker()
{
int cnt = 5;
while(cnt)
{
printf("这是子进程,pid:%d, ppid:%d, cnt: %d\n",getpid(), getppid(), cnt);
cnt--;
sleep(1);
}
}
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
Worker();
exit(0);
}
else
{
// sleep(10);
printf("wait before\n");
// pid_t rid = wait(NULL);
pid_t rid = waitpid(id,NULL,0);
printf("wait after\n");
if(rid == id)
{
printf("wait sucess, pid:%d\n",getpid());
}
}
return 0;
}
(2) status
status是一个int的整数,32bit,我们只考虑status的低16位。status的低16位被划分为三部分:
它的低七位是对应的信号,次低八位是退出的退出码。而剩下的1个bit位是core dump标志。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
void Worker()
{
int cnt = 5;
while(cnt)
{
printf("这是子进程,pid:%d, ppid:%d, cnt: %d\n",getpid(), getppid(), cnt);
cnt--;
sleep(1);
}
}
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
Worker();
exit(2);
}
else
{
// sleep(10);
printf("wait before\n");
// pid_t rid = wait(NULL);
int status = 0;
pid_t rid = waitpid(id,&status,0);
printf("wait after\n");
if(rid == id)
{
printf("wait sucess, pid:%d, status:%d\n",getpid(), status);
}
}
return 0;
}
思考:
我们设置的退出码是2,那么为什么status显示的是512呢?
退出码是2,根据上面status比特位的分区,我们可以写出status低16位:
那么该二进制转换成十进制,就是512。
所以,我们不能对status进行整体使用。
使用:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
void Worker()
{
int cnt = 5;
while(cnt)
{
printf("这是子进程,pid:%d, ppid:%d, cnt: %d\n",getpid(), getppid(), cnt);
cnt--;
sleep(1);
}
}
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
Worker();
exit(2);
}
else
{
// sleep(10);
printf("wait before\n");
// pid_t rid = wait(NULL);
int status = 0;
pid_t rid = waitpid(id,&status,0);
printf("wait after\n");
if(rid == id)
{
printf("wait sucess, pid:%d, rpid:%d, exit sig:%d, exit code:%d\n",getpid(), rid, status&0x7F, (status>>8)&0xFF);
}
}
return 0;
}
如此,我们就可以正确显示该进程的退出信号和退出码了。
总结:
- 当一个进程异常(收到信号),exit code是没有意义的。
- 判定有没有收到信号,exit sig:0(sucess)
- exit sig:0,exit code: 0 :代码跑完,结果正确。
- exit sig:0,exit code: 非0 :代码跑完,结果不正确。
- exit sig:非0 :中间出异常,exit sig的数字表明出现了哪种异常。