一.简要介绍( wait() ):
1.wait() :
pid_t wait(int *status);
1> wait() 的功能是阻塞等待子进程终止, 回收子进程资源, 调用 wait() 函数的进程会被挂起, 进入阻塞状态, 直到它的某个子进程变为僵尸态,但是该子进程的 PCB 还保留,内核在其中保留了一些信息:(1)若为正常终止,保留退出状态 (2)若为异常终止,保留使其终止的信号(关于退出的状态,我们可以在 shell 中使用特殊变量 $?来进行查看),父进程接收子进程退出状态信息,同时彻底清除掉这个进程。
父进程调用 wait() 或者waitpid() 函数用 status 传出参数来接收状态信息
若值为 -1 ,则回收失败;
若值为清除掉的子进程 id ,则回收成功
2> 父进程调用 wait() 函数可以回收子进程终止信息,此函数有三个作用:
(1)阻塞等待子进程退出
(2)回收子进程残留资源
(3)获取子进程结束状态(退出原因)
3>使用 wait() 传出参数 status 当其不空时(可以为空),来保存进程的退出状态, 退出状态存放在参数 status 的低 8 位中, 可通过 Linux 系统中提供的一组宏函数来获取,进一步判断进程终止的具体原因。 它们的参数与wait()函数相同, 都是一个整型的 status。 宏函数(常用的几个宏)的功能分别如下:
(1)WIFEXITED(status): 非0 -> 正常退出;
WEXITSTATUS(status): 如果上宏为真,则使用此宏 -> 获取进程退出状态(exit的参数);
(2)WIFSIGNALED(status): 非0 -> 进程异常终止;
WTERMSIG(status):如果上宏为真,则使用此宏 -> 获取使进程终止的那个信号的编号;
实例二. ( wait() )
1.利用 两种特殊的进程---孤儿进程和僵尸进程 中的僵尸进程实例加以 wait() 改写(未使用宏):
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/wait.h"
int main(){
pid_t pid,wpid;
printf("Begin...\n");
pid = fork();
if(pid == -1){ // pid 值为-1,则创建子进程失败
perror("fork error!");
exit(1);
}
else if(pid == 0){ // pid 值为0,则创建子进程成功
printf("i am child,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(3);
printf("child goes to die...\n");
}
else{ // pid 值为非负整数,则为父进程
wpid = wait(NULL);
if(wpid == -1){
perror("wait error!");
exit(1);
}
int i;
for(i=1;i<1000;i++){
printf("i am parent,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(1);
}
}
return 0;
}
在执行代码的过程中,开启另一个终端(terminal),并输入以下命令:
ps xua

将该结果同之前的僵尸进程结果截图进行比较,发现 子进程已经被回收
2.正常退出(使用正常退出宏)实例:
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/wait.h"
int main(){
pid_t pid,wpid;
int status;
printf("Begin...\n");
pid = fork();
if(pid == -1){ // pid 值为-1,则创建子进程失败
perror("fork error!");
exit(1);
}
else if(pid == 0){ // pid 值为0,则创建子进程成功
printf("i am child,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(3);
printf("child goes to die...\n");
exit(11); // 给子进程一个退出值
}
else{ // pid 值为非负整数,则为父进程
wpid = wait(&status);
if(wpid == -1){
perror("wait error!");
exit(1);
}
if(WIFEXITED(status)){
printf("child exits with %d\n",WEXITSTATUS(status));
}
int i;
for(i=1;i<100;i++){
printf("i am parent,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(1);
}
}
return 0;
}

由结果可知,当我们在代码中,让子进程以 exit(11) / return 11 正常退出时,第一组宏可获取子进程的退出状态
3.异常退出(使用异常退出宏):
#include"stdlib.h"
#include"unistd.h"
#include"sys/wait.h"
int main(){
pid_t pid,wpid;
int status;
printf("Begin...\n");
pid = fork();
if(pid == -1){ // pid 值为-1,则创建子进程失败
perror("fork error!");
exit(1);
}
else if(pid == 0){ // pid 值为0,则创建子进程成功
printf("i am child,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(60); // 想要在 60 秒 手工杀死程序
printf("child goes to die...\n");
exit(11);
}
else{ // pid 值为非负整数,则为父进程
wpid = wait(&status);
if(wpid == -1){
perror("wait error!");
exit(1);
}
if(WIFEXITED(status)){
printf("child exits with %d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status)){
printf("child is killed by %d\n",WTERMSIG(status));
}
int i;
for(i=1;i<100;i++){
printf("i am parent,pid = %u,ppid = %u\n",getpid(),getppid());
sleep(1);
}
}
return 0;
}

在执行代码的过程中,开启另一个终端(terminal)到相应目录下,并输入以下命令查看id号:
ps xua

并且执行下列命令将该子进程杀死并转回原先 terminal 查看:
kill 9 4805

由结果可知,当我们用 9 号信号杀死子进程的时候,使用第二组宏,可获取使该进程终止的信号的编号;当然,我们也可以使用别的信号来杀死子进程,输入下列命令,显示信号列表如下:
kill -l

三.简要介绍( waitpid() ):
1.waitpid():
pid_t waitpid(pid_t pid,int *status,int options);
1> 参数 pid 一般是进程的 pid, 但也会有其它取值。 参数 pid 的取值及其意义分别如下:
> 0 时, 回收指定 id 的子进程;
= -1 时,回收任意子进程( 作用同 wait() );
= 0 时, 回收和当前调用 waitpid() 一个组的所有进程;
< -1 时, 等待指定进程组中的任何子进程, 进程组的 id 等于 pid 的绝对值。
2> 参数 status , 同 上面的 wait() 一样,也可结合宏使用
3>参数 options ,options 提供控制 waitpid()的选项, 该选项是一个常量, 或由“ |” 连接的两
个常量。 该选项支持的选项如下:
(1) WNOHANG。 即使子进程没有终止, waitpid()也会立即返回,同阻塞回收
(2) WUNTRACED。 如果子进程暂停执行, 则 waitpid()立刻返回。
4>waitpid()函数的返回值会出现 3 种情况:
(1) 正常返回时 -> 获取子进程的 pid;
(2)若 options 的值为 WNOHANG, 但调用 waitpid()时发现没有已退出的子进程可收集,
-> 则返回 0;
(3) 若调用过程出错, 返回-1。 errno 会被设置成相应的值以指示错误位置。
四.实例
1.阻塞方法回收指定 pid 的子进程,代码及结果如下:
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/wait.h"
int main(){
pid_t pid,wpid;
printf("Begin...\n");
int i = 0;
for(i = 0;i < 5;i++){
pid = fork();
if(pid == -1){
perror("fork error!");
exit(1);
}
else if(pid == 0){
break;
}
else if(i == 3){
wpid = pid;
}
}
if(i < 5){
sleep(i);
printf("i am %d child,pid = %u\n",i+1,getpid());
}
else{
sleep(i);
printf("i am parent!\n");
waitpid(wpid,NULL,0); // 用 waitpid() 方法来回收指定 pid 为第四个的子进程,第三个参数为 0 ,默认为 阻塞 回收
while(1);
}
return 0;
}
在执行代码的过程中,开启另一个终端(terminal)到相应目录下,并输入以下命令查看id号:
ps xua

由结果可知,一共有四个僵尸进程,第四个子进程被回收
2.waitpid() 与 wait() 的同效(阻塞):
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/wait.h"
int main(){
pid_t pid,wpid;
printf("Begin...\n");
int i = 0;
for(i = 0;i < 5;i++){
pid = fork();
if(pid == -1){
perror("fork error!");
exit(1);
}
else if(pid == 0){
break;
}
else if(i == 3){
wpid = pid;
}
}
if(i < 5){
sleep(i);
printf("i am %d child,pid = %u\n",i+1,getpid());
}
else{
sleep(i);
printf("i am parent!\n");
while(waitpid(-1,NULL,0)); // 等价于 wait(NULL);
}
return 0;
}
在执行代码的过程中,开启另一个终端(terminal)到相应目录下,并输入以下命令查看id号:
ps xua

由结果可知,此时已经没有僵尸进程;此外,补充的是,每次调用一次 wait(),则对一个子进程进行回收,因而使用 while循环 来去回收子进程,而 waitpid 的返回值 在简要介绍中有写,可以根据此来进行理解。
3.非阻塞状态下进行回收
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/wait.h"
int main(){
pid_t pid,wpid;
printf("Begin...\n");
int i = 0;
for(i = 0;i < 5;i++){
pid = fork();
if(pid == -1){
perror("fork error!");
exit(1);
}
else if(pid == 0){
break;
}
}
if(i < 5){
sleep(i);
printf("i am %d child,pid = %u\n",i+1,getpid());
}
else{
sleep(i);
printf("i am parent!\n");
do{
wpid = waitpid(-1,NULL,WNOHANG);
if(wpid > 0)
i--;
}while(i > 0);
printf("finish\n");
}
return 0;
}

执行完代码之后,输入以下命令查看:
ps xua

由结果可知,子进程已经全部被回收;
--- 其余命令涉及到进程组,我们暂且不对其进行讨论
参考资料:
写在最后:
该博客是本人学习的一些总结,如果各位有不同见解,可以评论提出或者与我联系改正,谢谢大家!!!

1万+

被折叠的 条评论
为什么被折叠?



