Linux—进程、线程和进程通信(3)—进程的回收

目录

 

1、概念说明

僵尸进程

2、线程回收wait

函数说明

运行实例:

3、线程回收waitpid

函数说明

        运行实例:


 

1、概念说明

僵尸进程

        在之前提到了进程状态中,有提到“子进程资源未被及时回收,就会变成僵尸进程”。那么僵尸进程是什么意思呢?

        僵尸进程(Zombie Process)是指已经完成了执行任务的子进程,但是由于其父进程没有及时处理该子进程的完成状态,该子进程的进程控制块仍然被保留在系统中,占用了系统资源,但是无法被调用和执行任何任务,也无法被清除。

       僵尸进程会一直停留在系统中,直到其父进程终止或者处理该子进程的完成状态。如果系统中存在大量的僵尸进程,可能会导致系统资源的浪费和性能下降。

        为了避免僵尸进程,所以需要及时回收子进程资源。

 

2、进程回收wait

函数说明

##include<sys/types.h>//wait所需头文件
#include<sys/wait.h>//wait所需头文件

pid_t wait(int *status);

参数:

        status:用于保存子进程的返回值和结束方式,是一个指向保存地址的指针。

        (如果不关心子进程返回值,可以设为NULL,即不接收返回值)

        tips:这里提到的返回值,指的是函数退出函数“void exit(int XXX)”中的XXX(可以理解为子进程完成任务(进程退出)时,会给你留下一封信(返回值:XXX),你可以利用wait()将子进程安葬,并指定一个盒子(status)来装留下的信,这样你就可以看看子进程留下了什么。)

返回值:

        成功时,返回回收的子进程的进程号;失败时返回EOF     

注意点:

        需要注意的是:wait()是一个阻塞函数。

        简单来说:如果子进程一直没结束,父进程会一直阻塞。(就和堵车一样,前面的车不走,后面的车就只能一直等待)

        而且如果有多个子进程运行时,哪个先结束就先回收哪个子进程。

 

运行实例:

#include<stdio.h>                                                                                  
#include<unistd.h>
#include<stdlib.h>//exit所需头文件
#include<sys/types.h>//wait所需头文件
#include<sys/wait.h>//wait所需头文件

int main(void){
    
    pid_t pid;
    int i;
    int status;
    
    pid = fork();
    
    if(pid > 0){ 
        //以下为父进程A内容
        wait(&status);//用&status,存放子进程返回值
        printf("This is father pthread ID = %d\n", getpid()); 
        printf("ret = %d", status);//打印子函数返回值
    
    }else if(pid == 0){ 
        //以下为子进程B内容
        for(i = 0; i < 3; i++){
            printf("sleep %d\n", i); 
            sleep(1);
        };//休眠3秒,检查父进程是否阻塞 
        printf("This is child pthread ID = %d\n", getpid());
        exit(22);//子函数返回值设为22 
    }   

        程序思路:

        1、利用fork()函数,先让父进程(简称A)创建子进程(简称B)。

        2、子进程B:休眠3秒后,利用getpid()函数获取子进程进程号。然后利用exit()函数结束子进程B,并设置返回值为22。

        3、父进程A:利用wait()函数回收子进程B,并用&status获取函数返回值(由于阻塞的原因,父函数A直到子函数B结束后,才会继续执行)。然后利用getpid()函数获取进程号,打印子进程返回值。

运行结果如下:

861d6af00dcc4dc29154d962b07612d8.png

结果显示:

       1、 父进程在等待子进程结束后,才运行。哪怕子进程休眠了3s。

        2、ret = 5632不等于子进程设置的返回值22,那是函数出错了吗?

                答:其实并不是,就像之前举的例子:盒子(status)确实装了留下里的信(返回值),但是盒子里面还有其他的信息,需要宏函数来判断取值(也就是说:信被压在盒子底,需要依次打开隔间才能找到它)。

补充说明:

        wait函数中的status:当子进程正常回收时,返回相应返回值;异常回收时,返回相应信号值。

        可以通过三组宏函数(盒子有三个隔间),每组都需要先判断(是否为真),再利用后续函数打印取值。他们分别是:

//第一组(重点):
WIFEXITED(status)   //如果这个宏为真,代表: 进程正常结束,W表示wait,IF为如果
WEXITSTATUS(status) //如果上面的宏为真,可使用此宏来:获取进程退出状态 (exit的参数)

//第二组(重点):
WIFSIGNALED(status) //如果这个宏为真,代表:进程异常终止
WTERMSIG(status)    //如果上面的宏为真,可使用此宏来:取得使进程终止的那个信号的编号。

//第三组(了解即可):
WIFSTOPPED(status)  //如果这个宏为真,代表:进程处于暂停状态
WSTOPSIG(status)    //如果上面的宏为真,可使用此宏来:取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) //如果这个宏为真,代表:进程暂停后已经继续运行

  修改后代码如下:

#include<stdio.h>                                                                                  
#include<unistd.h>
#include<stdlib.h>//exit所需头文件
#include<sys/types.h>//wait所需头文件
#include<sys/wait.h>//wait所需头文件

int main(void){
    
    pid_t pid;
    int i;
    int status;
        
    pid = fork();
        
    if(pid > 0){ 
        //以下为父进程A内容
        wait(&status);
        printf("This is father pthread ID = %d\n", getpid());
            
        //此段进行修改
        if(WIFEXITED(status)){//判断第一个宏是否为真
            printf("ret = %d\n", WEXITSTATUS(status));//利用宏函数打印子函数返回值
        }   


    }else if(pid == 0){ 
        //以下为子进程B内容
        for(i = 0; i < 3; i++){
            printf("sleep %d\n", i); 
            sleep(1);
        };//休眠3秒,检查父进程是否阻塞 
        printf("This is child pthread ID = %d\n", getpid());
        exit(22);//子函数返回值设为22 
    }
    
    return 0;
}         

程序思路:

        对第一份代码进行修改,如下:       

        对父进程内容(21-23段)进行修改,采用WIFEXITED(status)判断是否为正常退出,使用WEXITSTATUS(status)获取子函数返回值

运行结果如下: 

54655758d9904cac8700d108922f512a.png

结果显示:

         ret = 22,为预定的返回值。

 

3、线程回收waitpid

        在上面我们介绍了使用wait()函数来进行进程回收,实际上是有很多不足的。还是拿堵车举例子:

        1、阻塞问题

        使用wait()回收进程时,子进程(B车)就相当于堵在了父进程(A车)前面。如果这个时候,B车设备故障熄火在路上了(即子进程出现bug,无法正常结束),A车就得一直在原地等(父进程阻塞)。

        这种事情显然不是我们所希望看到的,那么能否有办法让父进程查看子进程是否退出?如果子进程退出就回收子进程,如果没退出就继续运行父进程呢?(即不阻塞)

        2、无法指定回收的子进程

        现有父进程(A车)、1号子进程(B车)、2号子进程(C车),1号子进程运行时间比2号子进程运行时间短(B车需走5min,走完后打电话给A车;C车需走10min,走完后打电话给A车)。

        要求:父进程需要先回收2号子进程、再回收1号子进程(A车得先接C车的电话,再接B车的电话)。

        可以看到wait由于B车先打电话,所以A车肯定先接到B车电话。那我们能否在B车打电话的时候,不接他的电话,只等C车的电话呢?(即父进程,只回收指定的子进程,忽略其他子进程)

        那这就得介绍本次的主角:waitpid()函数

 

函数说明

##include<sys/types.h>//wait所需头文件
#include<sys/wait.h>//wait所需头文件

pid_t waitpid(pid_t pid, int *status, int option);

参数说明 

pid

        pid>0时,只等待进程ID等于pid的子进程。不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

        pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

        pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。

        pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

status采用宏函数调用,用法和wait()中相同,就不过多赘述了
options

WNOHANG :若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0

WUNTRACED: 返回终止子进程信息和因信号停止的子进程信息

0:不设置

tips:wait(&status) 等价于 waitpid(-1, &status, 0)。其中int status

返回值

        成功时,返回回收子进程的进程号;失败时,返回-1

运行实例:

#include<stdio.h>                                                                                  
#include<unistd.h>
#include<stdlib.h>//exit所需头文件
#include<sys/types.h>//wait所需头文件
#include<sys/wait.h>//wait所需头文件

int main(void){

    pid_t pid;
    int i;
    int status;

    pid = fork();
    
    if(pid > 0){
        //以下为父进程A内容
        

        waitpid(pid, &status, WNOHANG);//指定子进程号(pid返回子进程进程号)
                                       //status:存放子进程返回值
                                       //WNOHANG:设置为不阻塞


        printf("This is father pthread ID = %d\n", getpid());
        if(WIFEXITED(status)){//判断第一个宏是否为真
            printf("ret = %d\n", WEXITSTATUS(status));//利用宏函数打印子函数返回值
        }


    }else if(pid == 0){
        //以下为子进程B内容
        for(i = 0; i < 3000; i++){
            printf("sleep %d\n", i);
            sleep(1);
        };//休眠5min,检查父进程是否阻塞 
        printf("This is child pthread ID = %d\n", getpid());
        exit(22);//子函数返回值设为22 
    }
    
    

    return 0;
}                 

程序思路:

        对第二份函数进行修改如下:

        对父进程内容(19-23段)进行修改,采用waitpid(pid, &status,WNOHANG)设置为:指定回收子进程(父进程中,pid的是返回子进程进程号)、非阻塞(WNOHANG,若指定函数退出,则回收该函数;若指定函数还未退出,则忽略waitpid()函数)。

        对子进程内容进行修改,将休眠时间延长至5min

        由于子进程会休眠5min,所以父进程会忽略waitpid()函数。导致子进程无法被回收,父进程先于子进程结束后,子进程成为孤儿进程

运行结果如下: 

0344e4c1066e466b977d8859706fc875.png

 再新开一个终端,使用ps -elf|grep XXX的方式,查看XXX的进程状况。

 b297988ebac843e7a9ddf8ff6a2f10a8.png

 c6579726d858480d8bebd9b467512efa.png

结果显示:

        第一张图片显示:父进程先于子进程完成,非阻塞。而且waitpid()返回值为0。

        第二张图片显示:

                1、此时sleep仍然在打印,已经打印到了51

                2、此时子进程已被接管(由于我是用的不是root账号,所以接管父进程编号为1679,而不是1),成为了孤儿进程

                3、此时使用常规手段ctrl+c,已经无法关闭该进程

        第三张图片显示:

                需要使用kill -9 XXX(XXX为进程号)命令,来结束该进程。由于我这里的进程号为17899,所以在新开的终端里输入kill -9 17899,就可以结束该进程。

补充说明:

        可以看到,使用非阻塞方式回收进程有好也有坏,所以在编程时一定得注意提前规划好,避免临时调整导致bug不断。

        很多时候,调试bug比重新写程序花的时间还多。写出一个逻辑顺畅的程序,也是程序员的价值所在。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值