• 子进程的异步等待方式


      ⼀个进程在终⽌时会关闭所有⽂件描述符,释放在⽤户空间分配的内存,但它的PCB 保留着,内核在其中保存了⼀些信息:如果是正常终⽌则保存着退出状态,如果是异常终 ⽌则保存着导致该进程终⽌的信号是哪个。这个进程的⽗进程可以调⽤waitwaitpid 获取这些信息,然后彻底清除掉 这个进程。我们知道⼀个进程的退出状态可以在Shell ⽤特殊变量$?查看,因为Shell是它的⽗进程,当它终⽌时Shell调⽤waitwaitpid得到它的退出状态同时彻底清除掉这个进程。

      父进程调用wait()和waitpid()函数等待子进程时,⽗进程可以阻塞等待⼦进程结束,也可以⾮阻塞地查询是否有⼦进程结束等待清理(也就是轮询的⽅式)。采⽤第⼀种⽅式,⽗进程阻塞了就不 能处理⾃⼰的⼯作了;采⽤第⼆种⽅式,⽗进程在处理⾃⼰的⼯作的同时还要记得时不时地轮询⼀ 下,程序实现复杂。

    1.wait()和waitpid()

    (1)wait 函数:用来等待任何一个子进程退出,由父进程调用。

    1 #include<sys/types.h>
    2 #include<sys/wait.h>
    3 pid_t  wait(int* status);

    返回值:成功返回被等待子进程的pid,失败返回-1;

    status:输出型参数,拿回子进程的退出信息。不关⼼则可以设置成为NULL,如果参数status不为空,则进程终止状态被保存于其中;

    wait方式:阻塞式等待,等待的子进程不退出时,父进程一直不退出;

    目的:回收子进程,系统回收子进程的空间。

      依据传统,返回的整形状态字是由实现定义的,其中有些位表示退出状态(正常返回),其他位表示信号编号(异常返回),有的位表示是否产生了一个core文件等。 终止状态是定义在 sys/wait.h中的各个宏,有四个可互斥的宏可以用来取得进程终止的原因。

    WIFEXITED:若为正常终⽌⼦进程返回的状态,则为真。可以执行宏函数 WEXITSTATUS获取子进程状态传送给exit、_exit或_Exit的参数的低8位。(查看进程是否正常退出)
    WIFSIGNALED:若为异常终⽌⼦进程返回的状态(收到⼀个未捕捉的信号),则为真。可以执行宏函数WTERMSIG取得子进程终止的信号编号。另外,对于一些定义有宏WCOREDUMP宏,若以正常终止进程的core文件,则为真。
    WIFSTOPPED :若为当前暂停子进程的返回的状态,则为真,可执行WSTOPSIG取得使子进程暂停的信号编号。
    WIFCONTINUED:若在作业控制暂停后已经继续的子进程返回了状态,则为真,仅用于waitpid。

      举例: 1. 正常创建⽗⼦进程,⼦进程正常退出,⽗进程等待,并获取退出状status。调⽤该宏,查看输出结果(正常为⾮0,或1)。 2. 正常创建⽗⼦进程,⼦进pause(),⽗进程等待,并设置获取退出状态statuskill杀掉⼦进程。调⽤该宏,查看输出结果(结果为0)。2. WIFEXITED⾮零,返回⼦进程退出码,提取进程退出返回值,如果⼦进程调⽤exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫⽆意义.

    (2)waitpid:

    1 #include<sys/types.h>
    2 #include<sys/wait.h>
    3 pid_t waitpid(pid_t pid,int* status,int options);

    1>参数

    从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。
    pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
    pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样
    pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
    pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

    status参数与wait()函数的基本相同。

    options参数 当options参数为0时,与wait功能相同,仍是阻塞式等待,不提供额外功能,如果为下列常量按位或则提供更多功能:
    WCONTINUED:若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但状态尚未报告,则返回状态
    WNOHANG:若由pid指定的子进程并不是立即结束,则waitpid不阻塞,即此时以非阻塞方式(轮询式访问的必要条件)等待子进程,并且返回0。若正常结束,则返回该⼦进程的ID。
    WUNTRACED:若实现支持作业控制,而pid指定的任一子进程已经暂停,且其状态尚未报告,则返回其状态。

      说明: status 并不简简单单是⼀个整形变量,⽗进程和⼦进程之间所有的状态交互都要通过这个int来表⽰,所以这个int的若⼲bit位都是有特殊的含义的,那么这个“int”何编码就⽐较重要,和IP地址⼀样,它是⽐较紧凑的,或者说是⽐较拥挤的。status指出了⼦进程是正常退出还是被⾮正常结束的(⼀个进程也可以被其他进程⽤信号结束),以及正常结束时的返回值,或被哪⼀个信号结束或进程的退出码是多少等信息,这些信息都被放在整数的不同⼆进制中,所以⽤常规的⽅法读取会⾮常⿇烦,所以开发者就设计了⼀套专门的宏 来完成这项⼯作。

    2>返回值:
    当正常返回的时候,waitpid返回收集到的子进程的进程ID;
    如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
    如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
    当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;

    3>waitpid提供了三个wait所没有的功能:
    1. waitpid可等待一个特定的进程
    2. waitpid提供了一个wait的非阻塞版本
    3. waitpid支持作业控制

    2.关于SIGCHLD信号

      其实,⼦进程在终⽌时会给⽗进程发SIGCHLD信号,该信号的默认处理动作是忽略,⽗进程可以⾃ 定义SIGCHLD信号的处理函数,这样⽗进程只需专⼼处理⾃⼰的⼯作,不必关⼼⼦进程了,⼦进程 终⽌时会通知⽗进程,⽗进程在信号处理函数中调⽤wait清理⼦进程即可。一般情况下父进程收到这个信号的默认处理是忽略这个信号,即就是不做任何处理,但是我们可以通过系统调用API:signal()来进行自定义处理句柄,进行验证,具体代码如下:

    (1)验证子进程退出时会给父进程发送SIGCHLD信号

    程序完成以下功能:⽗进程fork出⼦进程,⼦进程调⽤exit(1) 终⽌,⽗进程⾃定SIGCHLD信号的处理函数,在其中调⽤wait获得⼦进程的退出状态并打印。

     用kill -l指令查看17号信号:

     3.子进程的异步等待方式

    (1)异步回收僵尸进程:
    fork()之后,非阻塞(异步)等待子进程(回收僵尸)。
    fork()之后,子进程和父进程分叉执行,僵尸进程的产生是因为父进程没有给子进程“收尸”造成的,又可以根据危害程度分为下述两类:
    总体来说:当子进程结束之后,但父进程未结束之前,子进程将成为僵尸进程。
    1)当子进程结束之后,但父进程未结束之前,子进程将成为僵尸进程,父进程结束后僵尸被init进程回收。
    2)如果子进程结束了,但是父进程始终没有结束,那么这个僵尸将一直存在,而且随着exec,僵尸越来越多。

    (2)实现代码

     1 #include<stdio.h> 
     2 #include<stdlib.h> 
     3 #include<signal.h> 
     4 #include<unistd.h> 
     5 #include<sys/types.h> 
     6 #include<sys/wait.h> 
     7 void catchSig(int sig) 
     8 { 
     9      printf("father is catching,child is quit
    "); 
    10      //以非阻塞方式等待所有异常退出的子进程 
    11      pid_t id; 
    12      while((id = waitpid(-1,NULL,WNOHANG)) > 0) 
    13      {    
    14          printf("wait child success:%d
    ",id); 
    15     } 
    16  }   
    17  int main() 
    18  {    
    19       signal(SIGCHLD,catchSig); 
    20       pid_t pid1 = fork(); 
    21       if(pid1 == 0)//child1 
    22       {    
    23           printf("child1:my pid is %d
    ",getpid()); 
    24           exit(-1);//子进程1异常终止 
    25       } 
    26       pid_t pid2 = fork();//child2 
    27       if(pid2 == 0) 
    28       { 
    29           printf("child2:my pid is %d
    ",getpid()); 
    30           exit(-1);//子进程2异常退出 
    31       } 
    32       pid_t pid3 = fork();//child3 
    33       if(pid3 == 0) 
    34       { 
    35           printf("child3:my pid is %d
    ",getpid());//子进程3正常运行  
    36       } 
    37       while(1) 
    38       {
    39          printf("father is waiting...
    "); 
    40          sleep(1); 
    41       } 
    42       return 0; 

    运行结果:

  • 相关阅读:
    管理者的四种不同授权风格
    centos7 未启用swap导致内存使用率过高。
    Dynamically create a div element with JavaScript/jQuery
    sql server: Parent/Child hierarchy tree view
    视频编解码系列(一)压缩编码基础常识
    mac 常用终端命令
    Linux下安装Python3.6.8
    SQL特殊字符转义
    Ehcache缓存监控
    指标管理体系设计
  • 原文地址:https://www.cnblogs.com/33debug/p/7017215.html
Copyright © 2020-2023  润新知