Linux——进程的等待

目录

  前言:

一.进程等待

父进程回收子进程信息的相关函数1:wait函数

        实验案例1:设置wait函数参数为NULL

        实验案例2:wait函数带wstatus参数的案例:当子进程正常运行完退出时

        情况3: wait函数带wstatus参数的案例:当子进程执行异常导致的终止时

  总结:

父进程回收子进程的相关函数2:waitpid();

           其中参数2status是重点要学习的!

扫描二维码关注公众号,回复: 15669939 查看本文章

           例1:使用waitpid函数——子进程运行途中出现异常错误的情况:

           例2:修改部分例1中子进程的代码:子进程运行途中运行正确,但结果不正确:

           阻塞等待VS 非阻塞等待:

二.总结:进程等待的必要性


  前言:

        一说到进程等待,就不得不提到僵尸进程了,有关僵尸进程,不了解的友友们可以看这篇快速了解一下什么是僵尸进程:Linux下的进程状态和 僵尸/孤儿进程的区别 _   中的Z状态


        在 Unix/Linux 系统中,正常情况下,子进程是通过父进程创建的,且两者的运行是相互独立的,父进程永远无法预测子进程到底什么时候结束。当子进程调用 exit 命令结束自己的生命时,其实它并没有真正的被销毁,内核只是释放了该进程的所有资源,包括打开的文件、占用的内存等,但是留下一个称为Z状态的数据结构,这个结构保留了一定的信息 (包括进程号 the process ID,退出状态、运行时间),这些信息直到父进程通过使用特定的系统调用函数获回收子进程的退出信息资源后,子进程才会被真正释放。这样设计的目的主要是保证只要父进程想知道子进程结束时的退出信息,主动进行回收子进程信息,就可以避免Z状态的进程。

一.进程等待

编译运行:

杀掉子进程后,代码运行结果:

通过while循环和ps ajx指令实时观察当前进程正在运行的结果: 


父进程回收子进程信息的相关函数1:wait函数

函数功能:

        父进程一旦调用wait函数就立即阻塞自己,即父进程立刻终止自己当前执行的一切指令,先对子进程的退出资源进行回收!由wait函数分析是否当前进程的某个进程已经退出,如果让它找到了这样一个已经变成zombie状态的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果wait函数没有找到,它就一直阻塞等待,直至找到一个结束的子进程或接收到了一个指定的信号为止,那么父进程的执行流也得一直跟着wait等待,wait什么时候找到僵尸状态的子进程返回给父进程,父进程什么时候才会继续执行自己的执行流!

 

        wstatus的作用就是用于保存子进程的状态信息,有了wstatus,父进程就可以了解子进程退出的原因,是正常退出还是有啥错误导致的退出!!!很重要! 

实验案例1:设置wait函数参数为NULL

进程状态检测器: 

篮框中表示:这段是父子进程正常运行的状态信息。

橙色框表示:这段是子进程退出后,父进程仍在运行的状态信息此时,pro_wait.exe进程退出,变为僵尸状态,等待着父进程的回收。

绿色框表示:这段就是子进程退出后,父进程采用wait函数“回收了子进程的资源”,父进程继续正常运行,此时子进程从僵尸状态-->死亡状态,真正被释放。


例1实行的方案图:


 实验案例2:wait函数带wstatus参数的案例:当子进程正常运行完退出时

知识点讲解:        

        WEXITSTATUS(status) :当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值。

        如果子进程调用exit(5) 退出,WEXITSTATUS(status)就会返回5;                                            如果子进程调用exit(7)WEXITSTATUS(status)就会返回7。

运行结果:


情况3: wait函数带wstatus参数的案例:当子进程执行异常导致的终止时

运行结果:

总结:

    非正常结束==异常结束 :

                WIFEXITED(status)==1时        等价于    WIFSIGNALED(status)==0时的情况

     非异常结束==正常结束:

                 WIFSIGNALED(status)==1时   等价于    WIFEXITED(status)==0时的情况 


 父进程回收子进程的相关函数2:waitpid();

        说完了wait0,我们再来说说waitpid(),可以说,waitpid是wait的升级版,具体我们看下面:

函数原型: pid t waitpid(pid t pid ,int *status , int options);

可见,waitpid相比于wait多了两个参数,下面具体来说一下这两个参数的用法。

参数1:

 参数2:

status:与wait的wstatus用法一样都是WIFE....· WIFS。
status详细说明:
        用于保存出发wait的信号status值或者退出时exit (code)中的code值。
        这个参数将保存子进程的状态信息,有了status,父进程可以了解子进程为什么会退出,它是正常退出还是异常退出。

参数3:

options :(常用的有两个)

  1.         0是默认行为,表示阻塞等待:进程要等待资源而得不到推进时,产生阻塞时等待(大部分的接口是阻塞的)
  2.         WNOHANG:如果没有子进程退出,立即返回,不会阻塞进程;如果结束了,则返回该子进程的进程号(return immediately if no child hasexited.)——表示为非阻塞时等待 (非阻塞式调用 )

     

        如果父进程在调用waitpid( 函数时,当指定等待的子进程已经停止运行或结束了,则waitpid() 会立即返回;但是如果子进程还没有停止运行或结束,则调用waitpid()函数的父进程则会被阻塞,暂停运行


其中参数2status是重点要学习的!

        wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
        如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图 (只研究status低16比特位) : 

        status 整型(一个整型无法表示多种退出状态),但整型有32个比特位,将其一分为二,高16位我们不必关心,低16位再一分为二,就变成了低七位和次低八位 (0-7位表示是否正常终止,8-15位为退出码) ,中间还有一位是core dump,这个不用管,没啥学的意义。

        status & 0x7F代表进程终止信号 ;(status >>8) & 0xFF代表进程退出码


        进程退出状态有三种:

1.运行结果完且正确、

2.运行完但结果不正确、

3.异常。

        进程结果是否正确由退出码决定,但异常情况下退出码无用,不过异常时会返回导致异常的信号编号。所以如果进程始终没有返回信号编号,则其退出非异常(正常退出)。 

        总结:

        所以说:进程运行成功,但结果的是否正确就只需要看:(status>>8) & 0xFF——status次低8位的值;

        进程运行途中,出现了各种错误(除零错误、段错误....),只需要看:status&0x7F——status低7位的值。

例1:使用waitpid函数——子进程运行途中出现异常错误的情况:

使用了错误语句——被动终止退出:

编译运行: 

        运行结果:因为代码中出现了除零错误,子进程会收到8号信号——SIGFPE(浮点运算错误信号),提前被终止运行。

例2:修改部分例1中子进程的代码:子进程运行途中运行正确,但结果不正确:

使用了exit函数,主动退出:

结果运行: 


接下来讲一讲waitpid函数中第三个参数option

上面说过option参数有两种选择,分别是阻塞式等待和非阻塞式等待:

阻塞等待VS 非阻塞等待:


        其一是0,0是option的默认选项,它代表着阻塞等待,即父进程会先一直等待子进程退出后才进行回收资源;
    

       阴塞等待的案例:比如张三想要叫李四请教问题,于是周未请他吃饭,张三去了李四楼下,然后打电话叫他,李四这时候正在忙着看书,电话中,张三说:“现在我请你去吃饭,你有时间没?  李四说:“我正在看书,你需要等10分钟。”,张三回说:“好的”,但是张三电话没挂,他俩一直在通话中,而张三一直在楼下等他。直到10分钟后,李四在电话中说了一句:“好了,可以了,我正在下楼”,张三看到李四下了楼,高兴的和他去吃饭了。

        

        在上面这个案例中,张三和李四一直处于通话中,张三一直在等李四下楼吃饭,没有干其他的事情,很专一!


        二是WNOHANG,它代表非阻塞等待,即父进程每隔一段时间就对子进程进行状态检测,会有次的状态检测-称为轮询。轮询过程中父进程不会把全部精力都投入到子进程中,它可以在等待的过程中,干些其他的事情!

       非阻塞等待的案例:张三还是想请教李四问题,还是周末请他吃饭,张三去了李四楼下,打电话叫他,李四这时候正在收拾家务,电话中张三说:“今天再一块吃个饭把,有些问题要请教你。”,李四说:“等我收拾完家务,你需要等会”,张三说:“好的”,基于上次漫长的等待,张三这次提前挂断了电话,刷起了抖音,在微信和朋友聊起了天,几分钟后,张三又打了个电话,问李四好了没,李四说仍在收拾:几分钟后,张三又做了同样的事,李四仍在忙;在这十几分钟中,张三给李四打了好几个电话,都在询问李四好了没。最后过了5分钟,李四下了楼,张三终于和李四去吃了饭。

        

        在这个案例中,张三经常打电话询问李四状态,这就是“轮询”,但相比第一次,张三没有一直和李四处于通话中,张三在等待过程中干着自己的事情。


        阻塞等待和非阳塞等待都有各自的好处,非阻塞是复杂一些,但父进程可以忙于处理其他进程,阻塞等待是简单但需要一直等该子进程处理完自己的事情才行。 

 

代码练习:1.阻塞式代码

#include<stdio.h>                                                                                                                                       
#include<string.h>
#include<sys/types.h>
#include<wait.h>
#include<stdlib.h>
   
   int main(){
       pid_t id= fork();
       assert(id>=0);
  
      int cnt=5;
      while(cnt){
          if(id==0){
              printf("我是子进程,pid:%d,ppid:%d,cnt:%d \n",getpid(),getppid(),cnt--);
          }
          sleep(1);
  
          if(cnt==0){
              exit(10);
          }
      }
  
      int status=0;
      while(1){
              pid_t ret=waitpid(id,&status,0);    //阻塞式等待 waitpid的第三参数为0代表是阻塞式
              printf("我是父进程,pid:%d,ppid:%d \n",getpid(),getppid());
              sleep(1);
              if(ret>0){
                  //父进程等待成功,子进程已退出!
                  printf("Father wait successly,Son have gone die !\n");
                  break;
             }
              else if(ret==0){                                                                                                                            
                  //父进程正在等待,子进程未退出!
                  printf("Father is waiting ,Son still done............ \n");
              }
 
              else{
                  //父进程调用waitpid失败
                  printf("Father takes waitpid() failly!\n");
              }
        }
      return 0;
  }

运行阻塞式代码结果: 

只有在子进程运行退出完之后,父进程才会开始执行它自己的代码——阻塞式。

代码练习:2.非阻塞式代码

#include<stdio.h>                                                                                                                                       
#include<string.h>
#include<sys/types.h>
#include<wait.h>
#include<stdlib.h>
   
   int main(){
       pid_t id= fork();
       assert(id>=0);
  
      int cnt=5;
      while(cnt){
          if(id==0){
              printf("我是子进程,pid:%d,ppid:%d,cnt:%d \n",getpid(),getppid(),cnt--);
          }
          sleep(1);
  
          if(cnt==0){
              exit(10);
          }
      }
  
      int status=0;
      while(1){
              pid_t ret=waitpid(id,&status,WNOHANG);    //非阻塞式等待 waitpid的第三参数为0代表是阻塞式
              printf("我是父进程,pid:%d,ppid:%d \n",getpid(),getppid());
              sleep(1);
              if(ret>0){
                  //父进程等待成功,子进程已退出!
                  printf("Father wait successly,Son have gone die !\n");
                  break;
             }
              else if(ret==0){                                                                                                                            
                  //父进程正在等待,子进程未退出!
                  printf("Father is waiting ,Son still done............ \n");
              }
 
              else{
                  //父进程调用waitpid失败
                  printf("Father takes waitpid() failly!\n");
              }
        }
      return 0;
  }

代码运行结果:

        waitpid设置为非阻塞式的等待时,父子进程会并发执行代码,父进程干着自己的事情,但是会每隔一段时间就询问子进程的退出情况——轮询! 


总结:进程等待的必要性


1、子进程退出,父进程如果不管不顾,就可能成“僵尸进程’的问题,进而造成内存泄漏。

2、进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。


3、父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。


4、父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。

 

猜你喜欢

转载自blog.csdn.net/weixin_69283129/article/details/130941243