wait waitpid WNOHANG 僵尸进程

来源:互联网 发布:手机上怎样查淘宝积分 编辑:程序博客网 时间:2024/05/16 16:12
什么是僵尸进程?
首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。
而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程。
任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称外僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。
如何避免僵尸进程
通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收
父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
如以下代码会创建100个子进程,但是父进程并未等待它们结束,所以在父进程退出前会有100个僵尸进程。
C
#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;
}
其中一个解决方法即是编写一个SIGCHLD信号处理程序来调用wait/waitpid来等待子进程返回。
C
#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;
}
但是通过运行程序发现还是会有僵尸进程,而且每次僵尸进程的数量都不定。这是为什么呢?其实主要是因为Linux的信号机制是不排队的,假如在某一时间段多个子进程退出后都会发出SIGCHLD信号,但父进程来不及一个一个地响应,所以最后父进程实际上只执行了一次信号处理函数。但执行一次信号处理函数只等待一个子进程退出,所以最后会有一些子进程依然是僵尸进程。
虽然这样但是有一点是明了的,就是收到SIGCHLD必然有子进程退出,而我们可以在信号处理函数里循环调用waitpid函数来等待所有的退出的子进程。至于为什么不用wait,主要原因是在wait在清理完所有僵尸进程后再次等待会阻塞。
所以最佳方案(个人观点)如下:
C
#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;

}



如果不理解用while循环接收signal,可以看看这篇文章  http://bbs.chinaunix.net/thread-828942-1-1.html


好比有五个进程,
不妨分别称为 p1 p2 p3 p4 p5,
一开始 p1 结束了,发了一个 SIGCHLD(s1),
这时父进程可能空闲了,于是开始处理这个信号,假设处理的过程中 p2 又结束了,又发了一个 SIGCHLD(s2),
这时候已经有两个信号了(一个正在处理,一个待处理),这时如果 p3 又结束了,那么它发的那个 SIGCHLD(s3) 势必会丢失,
丢失了怎么办?
没关系,因为那个信号处理函数是个循环嘛,
所以 while(waitpid()) 的时候,会把 p1 p2 p3 都处理的。
即使是很不幸,因为十分凑巧的原因,p3 没有被回收,导致变成僵尸进程了,也没关系,
因为还有 p4 p5 嘛,等到 p4 或者 p5 结束的时候,
又会再一次调用 while(waitpid()),到时候虽说这个 while(waitpid()) 是由 p4/p5 引起的,但是它也会一并把 p3 也处理的,因为它是个循环嘛!

如果还搞不懂,你就再看看 waitpid 的 man。

记住一点:
waitpid 和 SIGCHLD 没关系,即使是某个子进程对应的 SIGCHLD 丢失了,只要父进程在任何一个时刻调用了 waitpid,那么这个进程还是可以被回收的。

哎呀呀,简直费劲死了,其实说白了,就是一个“生产者-消费者”问题。
子进程结束的时候,系统“生产”出一个僵尸进程,
同时用 SIGCHLD 通知父进程来“消费”这个僵尸进程,
即使是 SIGCHLD 丢失了,没有来得及消费,
但是只要有一次消费,就会把所有的僵尸进程都处理光光!
(我再说一遍:因为,while(waitpid()) 是个循环嘛!)

0 0
原创粉丝点击