【Linux】进程控制2——进程等待(wait&&waitpid)

打印 上一主题 下一主题

主题 528|帖子 528|积分 1584

1. 进程等待的必要性——回收子进程


  • 子进程退出后,父进程如果不管不顾,就可能造成"僵尸进程”的问题,进而造成内存走漏
  • 另外,进程一旦变成僵尸状态(Z状态),那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们必要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
   什么是进程等待? 
  通过系统调用wait/waitpid,来举行对子进程举行状态检测与回收功能。 
我们先来创建一个僵尸进程
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. int main()
  5. {
  6.     pid_t id=fork();
  7.     if(id<0)
  8.     {
  9.         perror("fork\n");
  10.         return 1;
  11.     }
  12.     else if(id==0)
  13.     {
  14.     //child
  15.     int cnt=5;
  16.     while(cnt)
  17.         {
  18.             printf("I am child ,pid:%d,ppid :%d",getpid(),getppid());
  19.             cnt--;
  20.             sleep(1);
  21.         }
  22.         exit(0);//子进程在这里退出
  23.     }
  24.     else
  25.     {
  26.     //parent
  27.       while(1)
  28.         {
  29.             printf("I am father,pid:%d,ppid :%d",getpid(),getppid());
  30.             sleep(1);
  31.         }
  32.     }
  33. }
复制代码
监督脚本 
  1. while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v grep; sleep 1; echo "______________";done
复制代码
 

   这个父进程的父进程19897是谁啊?
  

  bash进程啊
   我们看到它这个子进程退出之后,父进程还没有退出,子进程不绝处于僵尸进程 
2 .wait系统调用接口

在Linux中,wait函数是一个系统调用用于等待子进程的停止并获取其停止状态。该函数的原型如下所示:
  1. #include<sys/types.h>
  2. #include<sys/wait.h>
  3. pid_t wait(int*status);
复制代码
wait函数返回值(PID)有以下几种可能的取值:


  • 如果乐成等待到一个子进程的停止,返回子进程的PID。
  • 如果调用进程没有子进程,wait函数会返回-1
  • 如果调用进程被一个信号停止,wait函数会返回-1
参数:(我们这里先不讲)


  •  输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
           函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当进步程的某个子进程已经退出,
  

  • 如果让它找到了这样一个已经变成僵尸的子进程,wait就会网络这个子进程的信息,并把它彻底销毁后返回
  • 如果没有找到这样一个子进程,wait就会不绝阻塞在这里,直到有一个出现为止。
  我们来看看两种情况来理解这个函数 
2.1.子进程等待父进程回收

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id=fork();
  9.     if(id<0)
  10.     {
  11.         perror("fork\n");
  12.         return 1;
  13.     }
  14.     else if(id==0)
  15.     {
  16.     //child
  17.     int cnt=5;
  18.     while(cnt)
  19.         {
  20.             printf("I am child ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  21.             cnt--;
  22.             sleep(1);
  23.         }
  24.         exit(0);//子进程在这里退出
  25.     }
  26.     else
  27.     {
  28.     //parent
  29.       int cnt=10;
  30.       while(cnt)
  31.         {
  32.             printf("I am father ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  33.             cnt--;
  34.             sleep(1);
  35.         }
  36.         //目前进程等待是必要的——回收僵尸进程
  37.         pid_t ret = wait(NULL);//我们先这么用着
  38.         if(ret == id)//id是父进程fork的返回值——子进程的PID
  39.         {
  40.             printf("wait success, ret : %d\n",ret);
  41.         }
  42.         sleep(5);
  43.     }
  44. }
复制代码
   当子进程停止后,wait函数会返回子进程的进程ID(PID) 
  我们分析一下程序:  前5秒的时间父子进程都在打印,5秒之后子进程先退,父进程 没有举行wait,还在打印,10秒之后父进程回收子进程,回收完后父进程还要继续等待5秒
我们看看运行结果

 我们看看隔壁的监控情况 


很符合我们的预期啊!!!
   wait一次是只能等待任意一个进程退出的,只不过我们这里只有一个子进程,以是只回收了这个子进程
   我们来看看怎么回收多个进程的
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. #define N 10
  7. void RunChild()
  8. {
  9.     int cnt =5;
  10.     while(cnt)
  11.     {   
  12.         printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
  13.        cnt--;
  14.         sleep(1);
  15.     }
  16. }
  17.         
  18. int main()
  19. {
  20.     int i;
  21.     for(i=0;i<N;++i)
  22.     {
  23.     pid_t id =fork();
  24.     if(id==0)
  25.         {
  26.      RunChild();
  27.         exit(0);//每个子进程走到这里就退出了
  28.         }
  29.     printf("creat child :%d  sucess\n",getpid());//这句话只有父进程会执行
  30.     }
  31.    
  32.     sleep(10);
  33.    
  34.     //等待,我们这里有10个子进程
  35.     for(i=0;i<N;++i)
  36.     {
  37.     pid_t ret =wait(NULL);
  38.     if(ret > 0)
  39.         {
  40.         printf("wait %d success\n",ret);
  41.         }
  42.     }
  43.     sleep(5);//父进程最后等一下
  44. }
复制代码
我们看看实行情况 
 

再看看监督情况
  1. ​while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v grep; sleep 1; echo "______________";done
复制代码
5个这个
 5个这个
 最后变这个

10个进程又怎么样呢?全给你回收了
2.1.2.父进程等待回收子进程

我们上面都是子进程等待父进程来回收,我们现在让父进程来等待回收子进程
   我们上面的例子都是子进程会退出,然后被wait函数回收,那么子进程要是不退出呢?
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. #define N 10
  7. void RunChild()
  8. {
  9.     while(1)//子进程永远不死
  10.     {   
  11.         printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
  12.         sleep(1);
  13.     }
  14. }
  15.         
  16. int main()
  17. {
  18.     int i;
  19.     for(i=0;i<N;++i)
  20.     {
  21.     pid_t id =fork();
  22.     if(id==0)
  23.         {
  24.      RunChild();
  25.         exit(0);//每个子进程走到这里就退出了
  26.         }
  27.     printf("creat child :%d  sucess\n",getpid());//这句话只有父进程会执行
  28.     }
  29.    
  30.     sleep(10);
  31.    
  32.     //等待,我们这里有10个子进程
  33.     for(i=0;i<N;++i)
  34.     {
  35.     pid_t ret =wait(NULL);
  36.     if(ret > 0)
  37.         {
  38.         printf("wait %d success\n",ret);
  39.         }
  40.     }
  41.     sleep(5);//父进程最后等一下
  42. }
复制代码
运行结果
 

 监督窗台,不绝都是下面这个

事实表明子进程不退的话,父进程就会在wait函数那边阻塞等待
    阻塞等待:如果子进程不退出,父进程默认在wait这个系统调用的地方不返回,这种状态叫阻塞状态
  综上, 我们可以得出

   来总结一番
    当父进程忘了用wait()函数等待已停止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.
  
    wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.
  
    如果先停止父进程,子进程将继续正常举行,只是它将由init进程(PID为1)继续,当子进程停止时,init进程捕获这个状态.
  
  3. waitpid方法

   waitpid函数是Linux中用于等待指定子进程停止的系统调用。
          与wait函数雷同,waitpid函数也可以用于获取子进程的停止状态。
  1. #include <sys/types.h>
  2. #inlclude <sys/wait.h>
  3. pid_ t waitpid(pid_t pid, int *status, int options);
复制代码
          函数功能是:父进程一旦调用了waitpid就立即阻塞自己,由waitpid自动分析是否当进步程的某个子进程已经退出,
  

  • 如果让它找到了这样一个已经变成僵尸的子进程,waitpid就会网络这个子进程的信息,并把它彻底销毁后返回
  • 如果没有找到这样一个子进程,waitpid就会不绝阻塞在这里,直到有一个出现为止。 
  从本质上讲,系统调用waitpid和wait的作用是完全雷同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。
下面我们就来具体介绍一下这两个参数:
   pid:
  从参数的名字pid和范例pid_t中就可以看出,这里必要的是一个进程ID。但当pid取差别的值时,在这里有差别的意义。

  •   pid>0时,只等待进程ID即是pid的子进程,不管别的已经有多少子进程运行竣事退出了,只要指定的子进程还没有竣事,waitpid就会不绝等下去。
  •   pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
  •   pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何答理。
  •   pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID即是pid的绝对值。
   options:
  options提供了一些额外的选项来控制waitpid,现在在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:
  1.  ret = waitpid(-1,  NULL,  WNOHANG | WUNTRACED);
复制代码
如果我们不想使用它们,也可以把options设为0,如:
  1.      ret = waitpid(-1,  NULL,  0);
复制代码


  • 如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永世等下去。
  • 而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之少少用到,这里就不多费笔墨了,有兴趣的读者可以自行查阅相关质料。
看到这里,聪明的读者可能已经看出端倪了:wait不就是经过包装的waitpid吗?
没错,察看<内核源码目录>/include/unistd.h文件349-352行就会发现以下程序段:
  1. static inline pid_t wait(int * wait_stat)
  2. {
  3.     return waitpid(-1,wait_stat,0);
  4. }
复制代码
  返回值和错误
  waitpid的返回值比wait稍微复杂一些,一共有3种情况:

  • 当正常返回的时间,waitpid返回网络到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可网络,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误地点;
   我们先看个简单的例子
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id=fork();
  9.     if(id<0)
  10.     {
  11.         perror("fork\n");
  12.         return 1;
  13.     }
  14.     else if(id==0)
  15.     {
  16.     //child
  17.     int cnt=5;
  18.     while(cnt)
  19.         {
  20.             printf("I am child ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  21.             cnt--;
  22.             sleep(1);
  23.         }
  24.         exit(0);//子进程在这里退出
  25.     }
  26.     else
  27.     {
  28.     //parent
  29.       int cnt=10;
  30.       while(cnt)
  31.         {
  32.             printf("I am father ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  33.             cnt--;
  34.             sleep(1);
  35.         }
  36.         //等价于pid_t ret = wait(NULL);
  37.         pid_t ret =waitpid(-1,NULL,0);//我们先这么用着
  38.         if(ret == id)//id是父进程fork的返回值——子进程的PID
  39.         {
  40.             printf("wait success, ret : %d\n",ret);
  41.         }
  42.         sleep(5);
  43.     }
  44. }
复制代码
 我们看看运行情况
  

  监督情况
  5个这个
  

  5个这个
  
 5个这个
  

  这个跟wait几乎一模一样
  我们也验证了返回值
   

  我们现在不来谈讨僵尸进程,我们现在来讨论子进程的退出码,这个是必要status参数的
  wait和waitpid都有这个status参数
  4..参数status——获得子进程退出情况

        当子进程停止后,wait函数会返回子进程的进程ID(PID),并将子进程的停止状态存储在指针status指向的变量中。


  • status参数是一个指向整型变量的指针,用于存储子进程的停止状态。
  • 通过status可以获取子进程的退出状态、停止信号等信息
  • 如果不关心停止状态,可以将status设置为NULL。
                                  status是个输出型参数
  什么叫输出型参数?就是函数调用竣事以后,会将参数的值写到这个变量里。
  
          换句话说,这个status是用来接收的,本质上不是用来传参的。
          我们把我们的status界说好了之后,放到该函数里,作为参数通报已往,函数调用完后,操作系统就会把status的值自动添补好,然后还给我们。实现的原理很简单,因为其用的是指针,通报的是变量的地址。倘若我们不关心这个status状态,那么直接通报NULL即可。
            但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就像下面这样:
  1. pid = wait(NULL);
复制代码
如果乐成,wait会返回被网络的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
  我们现在来相识一下这个参数的用法
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id=fork();
  9.     if(id<0)
  10.     {
  11.         perror("fork\n");
  12.         return 1;
  13.     }
  14.     else if(id==0)
  15.     {
  16.     //child
  17.     int cnt=5;
  18.     while(cnt)
  19.         {
  20.             printf("I am child ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  21.             cnt--;
  22.             sleep(1);
  23.         }
  24.         exit(1);//子进程在这里退出,注意是1
  25.     }
  26.     else
  27.     {
  28.     //parent
  29.       int cnt=10;
  30.       while(cnt)
  31.         {
  32.             printf("I am father ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  33.             cnt--;
  34.             sleep(1);
  35.         }
  36.         int status=0;//定义一个整型变量
  37.         //等价于pid_t ret = wait(NULL);
  38.         pid_t ret =waitpid(id,&status,0);//注意这个id
  39.         
  40.         if(ret == id)//id是父进程fork的返回值——子进程的PID
  41.         {
  42.             printf("wait success, ret : %d,status: %d\n",ret,status);//把status打印出来看看
  43.         }
  44.         sleep(5);
  45.     }
  46. }
复制代码
我们看看运行情况
 我们看看监控情况
5个这个

5个这个

5个这个

   留意:我们说的status,不是参数status,而是status指向的谁人整数 
    为什么status是256? 我们先不讲,先回答几个问题
     1.子进程在这里有几种退进场景?
  

    2.父进程盼望获得子进程的哪些信息?
  

  • 子进程代码是否异常呢
  • 没有异常,结果对吗?不对是因为什么?
  • 差别的退出码对应差别的错误原因
  一个参数要纪录这么多东西,我靠??? 吃得消吗?
正因为这个status这个天赋苦命圣体,以是它得被拆分成几个部分,分别纪录差别的信息
   留意:我们说的status,不是参数status,而是status指向的谁人整数  
  2.2.1.status 参数是位图结构 
   这个小小的数字,怎么存下这么多消息?
  它虽然是一个 int 型整数,但是不能简单地将其看作整型,而是被当作一个 位图结构 对待。
不过,关于 status 我们只必要关心该整数的 低 16 个比特位!
int有32个比特位,我们不必去关心它的高 16 位,因为依附低 16 位就足以判断了。
然而,整数的低 16 位,此中又可以分为 最低八位 和 次低八位(具体细节看图):

我们之研究status的低16比特位

  • 最低八位(包括core dump)存储的是停止信号(是否出异常的问题),本质就是该进程收到信号
  • 次低八位存储的是退出状态(退出状态)
 2.2.2、次低八位:存储了子进程退出码
重点:通过提取 status 的次低八位(倒数第9-16位),就可以拿到子进程的退出码。 
2.2.3、 最低七位:提取子进程的退出信号
重点:通过提取 status 的最低七位,就可以拿到子进程的退出信号。
我们的 status 的最低八位用于表示处理异常的地方,此中有 1 位是 core dump,我们等到信号那节再讲。
撤消 core dump,剩余七位用于进程中的退出信号,这就是 最低七位。
   进程退出,如果异常退出,是因为这个进程收到了特定的信号。
  

   退出状态,没有0号信号,进程信号是0就代表进程没有异常 
    有人就问了,搞这么复杂,能不能直接使用几个全局变量来实现这一功能?
  

  • 这是不行的,因为父子进程具有独立性,父子进程都会修改这个全局变量,父进程拿不到子进程的返回信息
   我们用代码
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id=fork();
  9.     if(id<0)
  10.     {
  11.         perror("fork\n");
  12.         return 1;
  13.     }
  14.     else if(id==0)
  15.     {
  16.     //child
  17.     int cnt=5;
  18.     while(cnt)
  19.         {
  20.             printf("I am child ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  21.             cnt--;
  22.             sleep(1);
  23.         }
  24.         exit(1);//子进程在这里退出,注意是1
  25.     }
  26.     else
  27.     {
  28.     //parent
  29.       int cnt=10;
  30.       while(cnt)
  31.         {
  32.             printf("I am father ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  33.             cnt--;
  34.             sleep(1);
  35.         }
  36.         int status=0;//定义一个整型变量
  37.         //等价于pid_t ret = wait(NULL);
  38.         pid_t ret =waitpid(id,&status,0);//注意这个id
  39.         
  40.         if(ret == id)//id是父进程fork的返回值——子进程的PID
  41.         {
  42.             //7F:0111 1111,status&7F就是status的倒数第七位
  43.             printf("wait success, ret : %d,exit sign: %d,exit code:%d\n",
  44.                     ret,status&0x7F,(status>>8)&0xFF);
  45.         }
  46.         sleep(5);
  47.     }
  48. }
复制代码
我知道各人对status&0x7F和(status>>8)&0xFF很好奇
    这个是退出信号的
  当status是一个32位(比特位)的整数,而且我们实行status & 0x7F操作时,我们现实上是在从status中提取其最低的7位。
  

  • 这里,0x7F在二进制中表示为01111111,它是一个8位的数,但只有最低7位是1。
  • 按位与操作(&)的特点是,只有当两个操作数的相应位都为1时,结果的该位才为1,否则为0。
  • 因此,status & 0x7F会保留status中最低7位的值,而将高于这7位的全部位都清零。
    这个是退出码的
  表达式 (status >> 8) & 0xFF 是一种位操作,这个表达式具体做了两件事情:
  

  • 右移操作 (>>)status >> 8 这部分将 status 的二进制表示向右移动8位。这意味着原来 status 中的最高8位(从左边数起)会被抛弃,而原来的低8位及其以下的全部位都会向左移动8位,原本不存在的位(即新出现的最高位)会被添补为0
  • 按位与操作 (&)(status >> 8) & 0xFF 这部分将上一步得到的结果与 0xFF 举行按位与操作。0xFF 在二进制中表示为 11111111,这是一个8位的数,全部位都是1。按位与操作的特点是,只有当两个相应的位都为1时,结果的该位才为1,否则为0。因此,与 0xFF 举行按位与操作现实上就是保留操作数的低8位,而将高于这8位的全部位都清零。
  综上所述(status >> 8) & 0xFF 这个表达式的目标是从 status 中提取出右移8位之后的新低8位(即原 status 的第9位到第16位,如果我们从0开始计数的话)。
  1. ​exitCode = (status >> 8) & 0xFF; //退出码
  2. exitSignal = status & 0x7F;      //退出信号
复制代码
 我们运行一下

   退出信号是0,退出码是1,这个1从那边来? 
   

这个exit(1)就是源头
    我们来让子进程出一次异常,看看会是什么情况?
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id=fork();
  9.     if(id<0)
  10.     {
  11.         perror("fork\n");
  12.         return 1;
  13.     }
  14.     else if(id==0)
  15.     {
  16.     //child
  17.     int cnt=5;
  18.     while(cnt)
  19.         {
  20.             printf("I am child ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  21.             cnt--;
  22.             sleep(1);
  23.         }
  24.         int a=10; a/=0;//注意这里
  25.         exit(1);//子进程在这里退出,注意是1
  26.     }
  27.     else
  28.     {
  29.     //parent
  30.       int cnt=10;
  31.       while(cnt)
  32.         {
  33.             printf("I am father ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  34.             cnt--;
  35.             sleep(1);
  36.         }
  37.         int status=0;//定义一个整型变量
  38.         //等价于pid_t ret = wait(NULL);
  39.         pid_t ret =waitpid(id,&status,0);//注意这个id
  40.         
  41.         if(ret == id)//id是父进程fork的返回值——子进程的PID
  42.         {
  43.             //7F:0111 1111,status&7F就是status的倒数第七位
  44.             printf("wait success, ret : %d,exit sign: %d,exit code:%d\n",
  45.                     ret,status&0x7F,(status>>8)&0xFF);
  46.         }
  47.         sleep(5);
  48.     }
  49. }
复制代码

实在子进程实行一次处0错误,就立马奔溃了
 异常信号是8,8是什么?

8就是浮点数异常
   为什么要通过系统调用来获得子进程的退出情况?父进程不能直接获取吗?
  父,子进程在操作系统中是相互独立的,都有自己的数据结构
        我们讲个故事,小张是A学校的,他的编程本领很强,隔壁B学校想让小张替他们学校去到场一次比赛,那就必须和A学校的校长沟通,才华让小张去到场
那么父进程就相称于B学校,子进程是小张
   进程在什么时间会等待失败?
  这个情况一样平常是等待的进程不是自己的子进程
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id=fork();
  9.     if(id<0)
  10.     {
  11.         perror("fork\n");
  12.         return 1;
  13.     }
  14.     else if(id==0)
  15.     {
  16.     //child
  17.     int cnt=5;
  18.     while(cnt)
  19.         {
  20.             printf("I am child ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  21.             cnt--;
  22.             sleep(1);
  23.         }
  24.         exit(1);//子进程在这里退出,注意是1
  25.     }
  26.     else
  27.     {
  28.     //parent
  29.       int cnt=10;
  30.       while(cnt)
  31.         {
  32.             printf("I am father ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  33.             cnt--;
  34.             sleep(1);
  35.         }
  36.         int status=0;//定义一个整型变量
  37.         //等价于pid_t ret = wait(NULL);
  38.         pid_t ret =waitpid(id+4,&status,0);//注意这个id
  39.         
  40.         if(ret == id)//id是父进程fork的返回值——子进程的PID
  41.         {
  42.             //7F:0111 1111,status&7F就是status的倒数第七位
  43.             printf("wait success, ret : %d,exit sign: %d,exit code:%d\n",
  44.                     ret,status&0x7F,(status>>8)&0xFF);
  45.         }
  46.         else
  47.         {
  48.         printf("wait filled\n");
  49.         }
  50.         sleep(5);
  51.     }
  52. }
复制代码

很明显了,等待失败了 
4.1、进程退出的宏

我们在上面使用status&0x7F和(status>>8)&0xFF来获取进程的退出信息,但是并不必要这么贫苦
我们今天写的代码,是通过位操作去截 status 得到退出码和退出信号的。
现实上,你也可以不消位操作,因为  已经给我们提供了一些宏供我们直接调用。
它们是 WEXITSTATUS 和 WIFEXITED,在这之前,我们再思索一个问题:
   思索:一个进程退出时,可以拿到退出码和退出信号,我们先看谁?
          一旦程序发现异常,我们只关心退出信号,退出码没有任何意义。
          以是,我们先关注退出信号,如果有异常了我们再去关注退出码。
    如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入此中, 这是一个整数值(int),指出了子进程是正常退出还是被非正常竣事的,以及正常竣事时的返回值,或被哪一个信号竣事的等信息。
        由于这些信息 被存放在一个整数的差别二进制位中,以是用通例的方法读取会非常贫苦,人们就筹划了一套专门的宏(macro)来完成这项工作,下面我们来学习一下此中最常用的两个:
   

  • 1,WIFEXITED(status) :用于检察进程是否正常退出,用来判断子进程是否为正常退出的,如果是正常停止的子进程返回状态,则为真。
          (请留意,虽然名字一样,这里的参数status并差别于wait唯一的参数——指向整数的指针status,而是谁人指针所指向的整数,切记不要搞混了。)
   

  • 2, WEXITSTATUS(status): 这个宏用来检察进程的退出码,,若非 0,提取子进程退出码。
  当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。
            请留意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,WEXITSTATUS(status)就毫偶然义。
  以是我们先判断程序也没有出异常,再看退出码,如果出现了异常,退出码就不看了
就先看WIFEXITED(status)!=0,如何打印 WEXITSTATUS(status)
我们看个例子 
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id=fork();
  9.     if(id<0)
  10.     {
  11.         perror("fork\n");
  12.         return 1;
  13.     }
  14.     else if(id==0)
  15.     {
  16.     //child
  17.     int cnt=3;
  18.     while(cnt)
  19.         {
  20.             printf("I am child ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  21.             cnt--;
  22.             sleep(1);
  23.         }
  24.         exit(1);//子进程在这里退出,注意是1
  25.     }
  26.     else
  27.     {
  28.     //parent
  29.       int cnt=5;
  30.       while(cnt)
  31.         {
  32.             printf("I am father ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  33.             cnt--;
  34.             sleep(1);
  35.         }
  36.         int status=0;//定义一个整型变量
  37.         //等价于pid_t ret = wait(NULL);
  38.         pid_t ret =waitpid(id,&status,0);//注意这个id
  39.         
  40.         if(ret == id)//id是父进程fork的返回值——子进程的PID
  41.         {
  42.             if(WIFEXITED(status)!=0)
  43.             {
  44.             printf("child is run successfull,exit code:%d\n",WEXITSTATUS(status));
  45.             }
  46.         }
  47.         else
  48.         {
  49.         printf("wait filled\n");
  50.         }
  51.         sleep(3);
  52.     }
  53. }
复制代码

完美啊!!!!!还不消起来??
我们再来看看多个子进程来
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. #define N 10
  7. void RunChild()
  8. {
  9.     int cnt =5;
  10.     while(cnt)
  11.     {   
  12.         printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
  13.        cnt--;
  14.         sleep(1);
  15.     }
  16. }
  17.         
  18. int main()
  19. {
  20.     int i;
  21.     for(i=0;i<N;++i)
  22.     {
  23.     pid_t id =fork();
  24.     if(id==0)
  25.         {
  26.      RunChild();
  27.         exit(i);//注意这里
  28.         }
  29.     printf("creat child :%d  sucess\n",getpid());//这句话只有父进程会执行
  30.     }
  31.    
  32.     sleep(10);
  33.    
  34.     //等待,我们这里有10个子进程
  35.     for(i=0;i<N;++i)
  36.     {
  37.     int status =0;
  38.     pid_t ret =waitpid(-1,&status,0);
  39.     if(ret > 0)
  40.         {
  41.         printf("wait %d success,exit code: %d\n",ret,WEXITSTATUS(status));
  42.         }
  43.     }
  44.     sleep(5);//父进程最后等一下
  45. }
复制代码

这就是进程等待,必须通过父进程来举行等待
5.option参数

当前设置等待的方式——有两种重要的等待方式


  • 参数0——阻塞等待
  • 非阻塞轮询
   5.1.阻塞等待——对应参数0

  

  •  如果子进程没有退出,而父进程在举行实行waitpid举行等待,阻塞等待。
  • 大部分IO类的函数比方scanf各种各样的接口,只要涉及IO的或多或少会可能出现阻塞的状态。
  • 现在所用的大部分接口都是阻塞接口(逻辑简单,容易实现)
  •  阻塞等待(Blocking Wait)在编程中通常指的是一个线程或进程在等待某个条件满足或某个操作完成之前,会暂停实行其他任务,处于等待状态。这种状态会不绝持续,直到等待的条件满足或操作完成,线程或进程才会继续实行后续的任务。在Java中,阻塞等待常用于多线程编程中,用于线程之间的同步和通信。
  5.2.阻塞等待和非阻塞等待的区分

场景:张三找李四告急帮他复习期末考试。
张三在李四的楼下等待李四就绪。
   
非阻塞等待

  张三每隔几分钟就给李四打电话扣问他是否就绪了,张三在没有打电话的时间看书/游戏/抖音
  

  • 就绪的过程本质就是非阻塞等待。
  • 张三非阻塞等待李四过程 == 函数调用
  • 张三给李四打电话 == 函数传参
  • 李四说等着没好 == 函数的返回值
  • 每次函数调用的本质是检测李四的状态(是否就绪)
  • 立即有返回值,多次等待,多次返回。
  

  • pid_ t waitpid(pid_t pid, int *status, WNOHANG);
  • pit_t == 0 :检测是乐成的,只不过子进程还没退出,必要你下一次举行重复等待。
  • pit_t > 0 :等待乐成,子进程退出了,而且父进程回收乐成。
  • pit_t < 0 :等待失败。
   
阻塞等待

          张三不绝给李四打着电话,直到李四就绪,期间张三不绝等待李四就绪,不敢别的事情。不绝检测李四的状态(不就绪,就不返回)
      不绝等待。直到子进程停止才返回。
  

  • pid_ t waitpid(pid_t pid, int *status, 0);
  • pit_t > 0 :等待乐成,子进程退出了,而且父进程回收乐成。
  • pit_t < 0 :等待失败。
  5.3. 非阻塞轮询——对应参数WNOHANG

   在子进程运行期间,父进程除了等待子进程或者是休眠,能不能干点其他的事情❓
          当然可以,在父进程等待,阻塞状态。可以通过设置options来让父进程干点事情。不阻塞等待而是非阻塞等待。
    什么是非阻塞等待呢?用代码该怎么去实现呢?
    非阻塞等待(Non-blocking Wait)则与阻塞等待相反。
  

  • 当线程或进程在等待某个条件满足或某个操作完成时,它不会暂停实行其他任务,而是会继续实行后续的任务。
  • 也就是说,即使等待的条件还没有满足或操作还没有完成,线程或进程也不会被阻塞,而是会继续实行其他的操作。
          通过设置options的宏值WNOHANG(wait no hang 等待没有阻塞 = 非阻塞等待)
  在盘算机中,"HANG" 通常指的是程序或系统出现无响应或停顿的状态,也就是常说的“卡住”或“死机”。
          当程序或系统由于某种原因(如资源锁定、死循环、死锁或外部系统交互问题等)而无法继续正常实行时,就可能会出现"HANG"的情况。这种情况下,用户可能无法与程序或系统举行交互,必要等待程序或系统恢复正常或举行重启操作。
          另外,在一些特定的语境下,"HANG" 也可能被用来形貌服务器或数据库的某些服务出现故障或无法访问的情况,这也可以被视为一种"宕机"征象。在这种情况下,"HANG" 指的是服务器或数据库的服务因为某种原因而制止响应或无法提供服务。
    具体操作
  

  • options这个参数只要一设置WNOHANG就会出现非阻塞等待。
  • 设置waitpid的WNOHANG本质上是检测一次进程的状态厘革。
  • 调用一次waipid就检测一次。每次调用都是检测,多次调用多次检测。
  • 非阻塞等待调用多次waitpid,调用waitpid检测是否退出等待过程无问题,只是子进程还未停止,必要等待下次等待。
    综上:非阻塞等待的时间 + 循环 = 非阻塞轮询
  
看个例子 


  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id=fork();
  9.     if(id<0)
  10.     {
  11.         perror("fork\n");
  12.         return 1;
  13.     }
  14.     else if(id==0)
  15.     {
  16.     //child
  17.     int cnt=5;
  18.     while(cnt)
  19.         {
  20.             printf("I am child ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  21.             cnt--;
  22.             sleep(1);
  23.         }
  24.         exit(1);//子进程在这里退出,注意是1
  25.     }
  26.     else
  27.    {
  28.     //parent
  29.         int status=0;//定义一个整型变量
  30.     while(1)
  31.     {   //等价于pid_t ret = wait(NULL);
  32.         pid_t ret =waitpid(id,&status,WNOHANG);//注意这个id
  33.         
  34.         if(ret >0)//等待成功
  35.         {
  36.             if(WIFEXITED(status)!=0)
  37.             {
  38.             printf("child is run successfull,exit code:%d\n",WEXITSTATUS(status));
  39.             }
  40.             break;//成功了就撤啊
  41.         }
  42.         else if(ret<0)//还需要等待
  43.         {
  44.         printf("wait filled\n");
  45.           break;//失败了就赶紧跑
  46.         }
  47.         else
  48.         {
  49.         printf("child is no exit,I am waitting...\n");
  50.         sleep(1);//父进程再等等子进程
  51.         }
  52.     }
  53.         sleep(3);
  54.    }
  55. }
复制代码
 

很完美吧
我们加了sleep(1)来加长父进程等待的时间。我们也可以不等待,也可以干别的事情
   我们只需在waitpid返回值为0的地方设置好父进程该干的事情即可
  我们来将等待换成别的任务
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id=fork();
  9.     if(id<0)
  10.     {
  11.         perror("fork\n");
  12.         return 1;
  13.     }
  14.     else if(id==0)
  15.     {
  16.     //child
  17.     int cnt=3;
  18.     while(cnt)
  19.         {
  20.             printf("I am child ,pid:%d, ppid :%d,cnt: %d\n",getpid(),getppid(),cnt);
  21.             cnt--;
  22.             sleep(1);
  23.         }
  24.         exit(1);//子进程在这里退出,注意是1
  25.     }
  26.     else
  27.    {
  28.     //parent
  29.         int status=0;//定义一个整型变量
  30.     while(1)
  31.     {   //等价于pid_t ret = wait(NULL);
  32.         pid_t ret =waitpid(id,&status,WNOHANG);//注意这个id
  33.         
  34.         if(ret >0)//等待成功
  35.         {
  36.             if(WIFEXITED(status)!=0)
  37.             {
  38.             printf("child is run successfull,exit code:%d\n",WEXITSTATUS(status));
  39.             }
  40.             break;//成功了就撤啊
  41.         }
  42.         else if(ret<0)//还需要等待
  43.         {
  44.         printf("wait filled\n");
  45.           break;//失败了就赶紧跑
  46.         }
  47.         else//干别的事情
  48.         {
  49.         int i=0;
  50.         for(;i<4;++i)
  51.         {
  52.         printf("Father is doing other things:%d\n",i);
  53.         sleep(1);
  54.         }
  55.     }
  56.         sleep(2);
  57.    }
  58. }
复制代码
 
怎么样呢?

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表