linux下的丧尸进度管理SIGCHLD能量信号,Linux内核学

2019-11-19 07:46 来源:未知

  进程调用 exit() 退出执行后,被设置为僵死状态,这时父进程可以通过 wait4() 系统调用查询子进程是否终结,之后再进行最后的操作,彻底删除进程所占用的内存资源。 wait4() 系统调用由 linux 内核实现,linux 系统通常提供了 wait()、waitpid()、wait3()、wait4() 这四个函数,四个函数的参数不同,语义也有细微的差别,但是都返回关于终止进程的状态信息。

  Linux中wait的用法:

转自:

1、wait() 函数:

  系统中的僵尸进程都要由wait系统调用来回收。

什么是僵尸进程

  wait() 函数的原型是:

  函数原型#include <sys/types.h>

首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。

#include <sys/types.h>        // 提供类型 pid_t 的定义
#include <sys/wait.h>

pid_t wait(int *status);

      #include <sys/wait.h>

而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程。

  当进程调用 wait() 时,会暂停目前进程的执行(即阻塞),由 wait() 来自动分析是否当前进程的某个子进程已经退出,如果找到了这样一个已经变成僵尸进程的子进程,wait 就会收集这个子进程的信息,并将其彻底销毁后返回;如果没有找到这样一个子进程,wait 就会一直阻塞在这里,直到出现僵尸进程。

      pid_t wait(int *status);

任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称外僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。

  参数 status 保存着子进程退出时的一些状态(包括 task_struct、thread_info及内核栈等)它是一个指向 int 类型的指针;如果不在意子进程的结束状态值,只想把这个僵尸进程消灭掉(实际上,大多数时候都是这样做的),则可以将这个参数设为 NULL,即:

  进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经推出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

 

pid = wait(NULL);        // 不管子进程的结束状态,直接杀死进程

  参数status用来保存被收集进程退出是的一些状态,他是一个指向int类型的指针。但如果我们对这个子进程是如何死掉并不在意,只想把这个僵尸进程消灭掉,我们可以设定这个参数为NULL,

僵尸进程的目的?

  如果 wait() 调用成功,则会返回被收集子进程的进程ID;如果被调用进程没有子进程,则调用失败,返回 -1

pid=wait(NULL);

设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(也就是说init进程将wait它们,从而去除它们的僵尸状态)。

  接下来用一段代码来演示一下 wait() 的用法:

如果收回成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被设置为ECHILD。

 

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 #include <stdlib.h>                                                                    
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 
  7 void main(){
  8     pid_t fpid,rpid;
  9     fpid = fork();
 10     if(fpid < 0){        
 11         perror("error on forking!n");
 12     }
 13     else if(fpid == 0){
 14         printf("this is a child process! the pid is %dn",getpid());
 15         sleep(3);
 16     }
 17     else{
 18         rpid = wait(NULL);          // 如果 wait()调用成功,则返回子进程的PID;如果调用失败,则返回 -1
 19         printf("Catch the child process with pid of %dn",rpid);
 20     }
 21     exit(0);
 22 }    

  如果参数status的值不是NULL,wait就会把子程序退出时的状态取出并存入其中,这是一个

如何避免僵尸进程?

输出结果如下:

整形值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪个信号结束的等信息。由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会变得非常麻烦,人们就设计了专门的宏(macro)来完成这项工作,下面是其中常用的两个:

  1. 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
  2. 父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞waitpid可以通过传递WNOHANG使父进程不阻塞立即返回
  3. 如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
  4. 通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。

图片 1

1,WIFEXITED(status)这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。(此处的status是指status指针所指向的整数)

第一种方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。

   关于 status 参数,比较复杂,暂时不做讨论,可以参考这里:

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

 

 

如果子进程调用exit(5)退出,WEXITSTATUS就会返回5;如果进程不是正常退出,也就是说

僵尸进程处理办法

2、waitpid() 函数:

返回0,这个值就毫无意义。

1 wait()函数

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

pid_t wait(int *status);

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。 
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:

  pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

  • wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
  • 返回的是子进程的PID,它通常是结束的子进程
  • 状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
  • 如果status不是一个空指针,状态信息将被写入它指向的位置

可以上述的一些宏判断子进程的退出情况:

图片 2

 

  函数原型:

  对于waitpid()函数来说,多出了两个可以由用户控制的参数pid和options。

2 waitpid()函数

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

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

参数:

status:如果不是空,会把状态信息写到它指向的位置,与wait一样

options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起

The value of options is an OR of zero or more  of  the  following  con- 
stants:

WNOHANG     return immediately if no child has exited.

WUNTRACED   also  return  if  a  child  has stopped (but not traced via 
            ptrace(2)).  Status for traced children which have  stopped 
            is provided even if this option is not specified.

WCONTINUED (since Linux 2.6.10) 
            also return if a stopped child has been resumed by delivery 
            of SIGCONT.

返回值:如果成功返回等待子进程的ID,失败返回-1

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

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

    #include <sys/types.h> /* 提供类型pid_t的定义 */

对于waitpid的p i d参数的解释与其值有关:

pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。

pid > 0 等待其进程I D与p i d相等的子进程。

pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。

pid < -1 等待其组I D等于p i d的绝对值的任一子进程

   waitpid() 函数的功能与 wait() 的功能类似,不过,它比 wait() 函数多了两个参数:

  #include <sys/wait.h>

wait与waitpid区别:

  • 在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
  • waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
  • 实际上wait函数是waitpid函数的一个特例。waitpid(-1, &status, 0);

 

示例:

如以下代码会创建100个子进程,但是父进程并未等待它们结束,所以在父进程退出前会有100个僵尸进程。

图片 3

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

int main() {  

  int i;  
  pid_t pid;  

  for(i=0; i<100; i  ) {  
    pid = fork();  
    if(pid == 0)  
      break;  
  }  

  if(pid>0) {  
    printf("press Enter to exit...");  
    getchar();  
  }  

  return 0;  
}  

图片 4

其中一个解决方法即是编写一个SIGCHLD信号处理程序来调用wait/waitpid来等待子进程返回。

 

图片 5

#include <stdio.h>  
#include <unistd.h>  
#include <signal.h>  
#include <sys/types.h>  
#include <sys/wait.h>  

void wait4children(int signo) {  

  int status;  
  wait(&status);  

}  

int main() {  

  int i;  
  pid_t pid;  

  signal(SIGCHLD, wait4children);  

  for(i=0; i<100; i  ) {  
    pid = fork();  
    if(pid == 0)  
      break;  
  }  

  if(pid>0) {  
    printf("press Enter to exit...");  
    getchar();  
  }  

  return 0;  
}  

图片 6

但是通过运行程序发现还是会有僵尸进程,而且每次僵尸进程的数量都不定。这是为什么呢?其实主要是因为Linux的信号机制是不排队的,假如在某一时间段多个子进程退出后都会发出SIGCHLD信号,但父进程来不及一个一个地响应,所以最后父进程实际上只执行了一次信号处理函数。但执行一次信号处理函数只等待一个子进程退出,所以最后会有一些子进程依然是僵尸进程。

虽然这样但是有一点是明了的,就是收到SIGCHLD必然有子进程退出,而我们可以在信号处理函数里循环调用waitpid函数来等待所有的退出的子进程。至于为什么不用wait,主要原因是在wait在清理完所有僵尸进程后再次等待会阻塞。

 

所以最佳方案如下:

图片 7

#include <stdio.h>  
#include <unistd.h>  
#include <signal.h>  
#include <errno.h>  
#include <sys/types.h>  
#include <sys/wait.h>  

void wait4children(int signo) {  
  int status;  
  while(waitpid(-1, &status, WNOHANG) > 0);  
}  

int main() {  

  int i;  
  pid_t pid;  

  signal(SIGCHLD, wait4children);  

  for(i=0; i<100; i  ) {  
    pid = fork();  
    if(pid == 0)  
      break;  
  }  

  if(pid>0) {  
    printf("press Enter to exit...");  
    getchar();  
  }  

  return 0;  
}  

图片 8

这里使用waitpid而不是使用wait的原因在于:我们在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,它告诉waitpid在有尚未终止的子进程在运行时不要阻塞。我们不能在循环内调用wait,因为没有办法防止wait在正运行的子进程尚有未终止时阻塞。

1)参数 pid 为欲等待的子进程的识别码:

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

  pid < -1 ;等待进程组 ID 为 pid 绝对值的进程组中的任何子进程;

  pid>0时,只等待进程ID等于pid的子进程,不管其他已经有多少子进程运行结束退出了,只要

  pid = -1 ;等待任何子进程,此时 waitpid() 相当于 wait()。实际上,wait()就是 pid = -1、options = 0 的waitpid(), 且有:

指定的子进程还没有结束,waitpid就会一直等下去。

static inline pid_t wait(*status){
    return waitpid(-1,*status,0);  
}

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

  pid = 0 ;等待进程组 ID 与当前进程相同的任何子进程(也就是等待同一个进程组中的任何子进程);

  pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入别的进程组,waitpid不

  pid > 0 ;等待任何子进程 ID 为 pid 的子进程,只要指定的子进程还没有结束,waitpid() 就会一直等下去。

会对它做任何理睬。

2)参数 options 提供一些额外的选项来控制 waitpid():

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

  WNOHANG;如果没有任何已经结束了的子进程,则马上返回,不等待;

options:

  WUNTRACED;如果子进程进入暂停执行的情况,则马上返回,但结束状态不予理会;

  如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不像wait

  也可以将这两个选项组合起来使用,使用 OR 操作。如果不想使用这两个选项,也可以直接把 options 设为0 ,如下:

那样永远等下去。

waitpid(-1,NULL,WNOHANG | WUNTRACED);     // 没有任何已结束了的子进程或子进程进入暂停执行的状态,则马上返回不等待
waitpid(-1,NULL,0);                // options 设为0,则 waitpid() 会一直等待,直到有进程退出

  waitpid返回值:

3)waitpid() 的返回值,有三种:

  当正常返回的时候,waitpid返回收集到的子进程的进程ID;

a)正常返回时,waitpid() 返回收集到的子进程的PID;

  如果设置了选项WNOHANG,而调用waitpid发现没有已经退出的子进程可收集,则返回0;

b)如果设置了 WNOHANG,而调用 waitpid() 时,没有发现已退出的子进程可收集,则返回0;

  如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误的所在;当pid所

c)如果调用出错,则返回 -1,这时erron 会被设置为相应的值以指示错误所在。(当 pid 所指示的子进程不错在,或此进程存在,但不是调用进程的子进程, waitpid() 就会返回出错,这时 erron 被设置为 ECHILD)

指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时

 

errno被设置成ECHILD。

  1 #include <sys/types.h> 
  2 #include <sys/wait.h>
  3 #include <unistd.h>
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6
  7 void main(){
  8     pid_t fpid,rpid;                          // fpid为fork()的返回值,rpid为waitpid()的返回值
  9     fpid = fork();
 10     if(fpid < 0){
 11         printf("error on forking");
 12     }
 13     else if(fpid == 0){                       // 子进程中 fork() 返回值为0
 14         printf("this is a child process,pid is %dn",getpid());
 15         sleep(10);                            // 睡眠10s,10s 后子进程退出
 16         exit(0);
 17     }
 18     do{                                  // 父进程中,fork()返回新创建子进程的 PID
 19         rpid = waitpid(fpid,NULL,WNOHANG);    // 等待 PID = fpid 的进程(即子进程)退出,设置了WNOHANG选项,表明当没有发现已退出的子进程时不用等待直接返回,返回值为0;
 20         if(rpid == 0){                        // rpid = 0,说明没有发现已退出的子进程
 21             printf("No child exitedn");
 22             sleep(1);
 23         }
 24     }while(rpid == 0);
 25     if(fpid == rpid)                         // 成功收集了退出的子进程,返回值为被收集子进程的PID
 26         printf("successfully get child process %dn",rpid);
 27     else
 28         printf("error!n");
 29 }     

  

结果如下:

 

图片 9

  从结果中可以看到,在子进程休眠的10s时间里,waitpid() 并没有一直等待,而是直接返回0,然后做自己的事情(睡眠1s),如此重复了10次;当子进程退出时,waitpid() 收集到退出的子进程,并返回所收集子进程的PID。

 

 3、wait3()、wait4() 函数:

  函数原型:

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

pid_t wait3(int *status,int options,struct rusage *rusage);
pid_t wait4(pid_t pid,int *status,int options,struct rusage *rusage);

   wait3() 和 wait4() 函数除了可以获得子进程状态信息外,还可以获得子进程的资源使用信息,这些信息是通过参数 rusage 得到的。而 wait3() 与 wait4() 之间的区别是,wait3() 等待所有进程,而 wait4() 可以根据 pid 的值选择要等待的子进程,参数 pid 的意义与 waitpid() 函数的一样。

 

 本文主要参考:

 

 

 

 

TAG标签:
版权声明:本文由吉利彩票平台注册-吉利彩票平台官方注册-官网推荐发布于吉利彩票平台注册,转载请注明出处:linux下的丧尸进度管理SIGCHLD能量信号,Linux内核学