您的位置:首页 > 运维架构 > Linux

Linux父进程对于子进程的异步等待

2017-06-27 16:59 295 查看

父进程为什么等待子进程

  

  Linux中,存在多进程、多线程的服务。

  进程是如何产生、如何消失的?

  首先,打开Linux虚拟机或者系统的时候,系统自己建立一个init进程,这是Linux系统的基础进程,然后init进程再根据一些配置文件决定创建哪些进程,或者我们在终端自己创建一个新进程的时候,如果你有时间,你可以一直追查这个进程的祖祖辈辈,你会发现,他们都指向一个进程,那就是init进程。所以,可以这么说,Linux系统服务中,所有进程都是init的子孙进程。所以,进程的创建是通过创建子进程一个一个实现的。

  但是,一味的创建进程,而不对进程进行合理的管理,必然会造成操作系统的冗余,长时间下来,因为无用进程过多,系统将会崩溃。因为每个进程需要对自己维护一个PCB的结构体,来描述一个进程所有可能用到的资源、标志…所以,这个结构体并不小,且每个进程都自己私有一个,所以对无用进程的PCB管理是很有必要的。

  操作系统把无用或退出进程的PCB管理交给了他的父进程,每个拥有子进程的父进程函数体中都有一个等待操作,需要对他创建的子进程进行管理,毕竟,父亲管儿子天经地义嘛!所以,父进程在执行完自己的所有操作后,会以阻塞或者轮询的方式等待子进程的退出,收到子进程的退出数据之后,通过一定的手段,对子进程的PCB进行资源的释放。

  根据之前的认识,可能一下子就会想到两个函数

  

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

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


  这两个函数是父进程对子进程的等待函数,成功的返回值都是子进程的pid,直到收到这个pid,系统就会对这个pid对应的PCB进行回收,资源的释放的等操作,保证系统的效率。

  

wait和waitpid的区别与相同之处

wait

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);


  1、wait函数只会进行阻塞式等待子进程;

  

  2、wait函数只有一个参数status,代表进程的退出方式,如果设置为空(NULL),代表不关心进程的退出方式与退出码。

  status使用bit位表示他的含义,例如,他的次低8为表示进程的退出码,低8位表示进程是否征程退出。为了使用简单,所以操作系统提供了一部分接口。

  

WIFEXITED(status)//为真时表示进程正常退出

WEXITSTATUS(status)//表示进程退出的退出码


waitpid

#include <sys/types.h>
#include <sys/wait.h>

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


  pid表示waitpid函数需要等待的进程id号:

  从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

pid的取值代表含义
pid>0等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去
pid=-1等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样
pid=0等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬
pid<-1等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值
 

  status同wait函数;

options作用
WNOHANG即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。(即轮询等待)
WUNTRACED跟踪
  

  所以,父进程等待子进程的函数就是这两个函数,虽然waitpid有轮询方式等待子进程,但是隔一段时间来轮询检查一次子进程,还是很浪费资源,所以我们需要更简单、更省资源的做法。

  

  

子进程的退出时对父进程发出信号

  子进程的退出其实是会给父进程发出一个信号的,恰巧在kill的信号列表里,可以发现这个信号。

  


  SIGCHLD是不是很眼熟,那不就是 signal 和 child的简写吗?所以有理由相信,子进程退出时会对父进程发出一个SGICHLD的信号。所以现在使用signal函数对SIGCHLD进行一次捕捉

  

对子进程退出时信号的捕捉

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handler(int sig)
{
printf("%d\n",sig);
}

int main()
{
int i=1;
for(;i<=31;i++){
signal(i,handler);
}
printf("\n");
pid_t id=fork();
if(id<0){
perror("fork");
exit(1);
}else if(id==0){
printf("i am a child:%d,my father is %d\n",getpid(),getppid());
exit(2);
}
printf("i am father:%d\n",getpid());
sleep(1);
return 0;
}


输出结果:



捕捉子进程SIGCHLD后进行等待

  现在已经验证子进程退出时会对父进程发出一个SIGCHLD的信号,所以我们可以对这个信号进行捕捉,自定义的信号处理方式表位等待一个子进程,所以这就省去了对于一个子进程的阻塞等待或者轮询等待时资源的耗费。

  代码实现

  

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

void handler(int id)
{
int status;
if (waitpid(-1,&status,0)<0 ){
perror("waitpid");
exit(3);
}
printf("handler func wait success!\n");
}

int main()
{
signal(SIGCHLD,handler);
pid_t id=fork();
if(id<0){
perror("fork");
exit(1);
}else if(id==0){//child
printf("i am child :%d\n",getpid());
exit(2);
}else{
printf("i am father :%d\n",getpid());
sleep(1);
}
return 0;
}


输出结果



  

  如果在handler函数中将判断的if条件改为while循环则可以等待多个子进程。

  

void handler(int id)
{
int status;
while (waitpid(-1,&status,0) ){
printf("handler func wait success!\n");
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: