浅谈Linux进程等待

2019-07-14 12:19发布

首先我们要想到两个问题:

1、进程为什么等待?
2、进程怎么等待?

为什么等待:首先要知道进程终止或者退出的时候会发生什么,进程会关闭所有文件描述符,释放在用户空间分配的内存,但是PCB却会暂时保留,里面存了一些信息,假如這个进程运行结束,不论结果正确与否,只要是正常终止(反正进程是把代码跑完了),PCB里面就放着进程的退出状态(退出码),如果是异常终止(肯定是收到信号了),那么PCB里面存放着导致该进程终止的信号。我们知道,子进程的退出状态必须由父进程回收,也就是说,父进程必须得等待子进程(這个我们在学习进程最基础的时候就是意识里面的了),父进程要是不等先驾鹤西去了,那么子进程就成了孤儿进程等待领养,领养的那位还是得等,不管咋样只要是有子进程的进程,都得等,原因只有一个:父进程要拿到子进程的退出状态,然后释放子进程的僵尸状态,彻底清除掉這个进程(pcb没了,链表push) (当一个进程终止(不管正常异常)时候,内核会向其父进程发送信号,通知父进程要等,父进程调用wait)
怎么等待: 两个接口:1. pid_t wait(int* status);2.pid_t waitpid( pid_t pid, int *status ,int options);
注:可以使用$?是查看正常退出的退出码

补充:关于进程的退出状态
常见两种:
1、运行完毕,结果准确,退出码0
2、运行完毕,结果错误,退出码非0
第三种情况:进程异常退出(原因:进程收到一个退出的信号),所以对于进程等待,需要注意是否收到退出信号(异常),收到退出码(正常运行完毕),当然這个在后面是可以使用宏来判断的。

系统调用

1.wait系统调用pid_t wait(int* status);
头文件:
#include
#include
参数:一个 int* status 输出型参数,整型指针,指向的空间即存放退出子进程的推出状态,所以它就是获取子进程推出状态的,不关心可以设置位NULL;
注意:父进程调用wait,是等待任意一个已经退出的子进程,只要是有子进程退出了,那么就获取子进程退出状态然后返回。
注意:這中等待方式是阻塞式等待 ,也就是说如果父进程调用了wait函数,那么父进程啥都不干了自己的代码不跑了就坐在那等退出的子进程,目前要是没有要退出的子进程,,父进程就一直等着,這就是阻塞式等待;那么如果有子进程退出了,那么就获取推出状态返回。当然,如果父进程是收到了SIGCHLD信号,那么它肯定是立马调wait立马返回然后立马清理然后没了,我们在任意时刻调用wait必定很可能是会阻塞的。
返回值:我们能看出来返回值是pid,如果返回值>0,可想而知必然是返回的子进程的进程id,可以通过status指针看看子进程退出状态(如果status不为空)。
如果返回值是-1,则等待失败,为啥会等待失败:原因之一就是调用wait函数的进程本身就没有子进程,(还有這种操作?)、调用出错。
总之:wait系统调用是父进程为了等待子进程退出而调用的,在任意时刻调用wait会使得父进程阻塞,所以這是一种阻塞式调用。

2.waitpid系统调用pid_t waitpid( pid_t pid, int *status ,int options);
头文件:和上面wait一样。
参数:第一个参数我们可以猜到,是要等的那某个进程id等于pid的子进程(pid>0),指定要等哪一个,第二个参数意义和wait调用如出一辙,第三个参数是一个选项,如果options设置为0,那么這个函数就是阻塞式等待,如果设置options为 WNOHANG,waitpid发现没有已经退出的子进程可以收集,就返回0。這就是非阻塞式等待
那么這里我们就能明显感觉到waitpid和wait的区别:waitpid是等待指定的子进程退出,而wait是等到谁就是谁,wait在等待的时候就是阻塞,而waitpid如果等不到就立马返回继续干自己的事情,是一种非阻塞式等待。
如果第一个参数pid==-1,那么就是等待任意一个子进程,和wait等效。
返回值:如果返回值>0,则等待成功,返回是子进程的id,如果==0,那么就是发现等待的那个子进程没有退出,则立即返回0,如果==-1,则调用失败。

补充:

#轮询的概念:定期循环的检测等待的措施是否准备就绪(一定要基于非阻塞式调用),在代码实现的时候,如果使用waitpid外面嵌套一个条件,如果返回值==0那么就让父进程自己做自己的事情,隔一段时间调用waitpid,如果子进程返回了那么ok,這样就实现了轮询。
关于WIFEXITEDWEXITSTATUS這两个宏:
注意:這两个宏对于wait和waitpid都是可用的)
可以用這两个宏来检查wait和waitpid所返回的子进程的退出状态:
如果WIFEXITED(status) 非0,则说明进程是正常退出的,不是异常退出的,那么我们可以使用WEXITSTATUS(status)来查看或者提取进程退出的返回值,例如:子进程要是调用exit(3),那么WEXITSTATUS(status)返回值就是3。
注意:如果如果WIFEXITED(status)为0,wait或者waitpid等待的那个子进程是异常退出,收到了信号而终止了运行。也只能说明這一点,這个时候的WEXITSTATUS(status),是毫无意义的,它只能返回正常退出的子进程的退出码,這一点要清楚。
总结:从整个操作系统来看,shell是一个老大程序,我们运行的程序都是shell的子进程,当一个进程结束时,shell调用了wait或者waitpid得到进程的推出状态然后彻底清除进程,所以说我们可以通过$?查看最近一个退出的进程的退出状态。

测试用例

使用wait进行等待(阻塞式):
这里写图片描述
运行结果:

使用waitpid进行等待(非阻塞式):
这里写图片描述
运行结果:
这里写图片描述