避免僵尸进程的方法

避免僵尸进程的方法

何为僵尸进程

  • 如果父进程先退出,子进程自动被 init 进程收养,不会产生僵尸进程。
  • 如果子进程先退出。父进程 wait() 处理(即父进程调用wait/waitpid方法来处理),则僵尸进程会被父进程清理;如果父进程不用 wait() 处理,则僵尸进程会在父进程退出之前一直存在。当然,父进程退出后,僵尸子进程会被 init 收养,init 进程会自动调用 wait() 处理。但是对于处理网络请求的服务器进程来说,父进程可能会一直存在,子进程处理完任务就退出,这种情况下会产生很多僵尸进程,这种场景就需要对僵尸进程的处理提高警惕了。

避免产生僵尸进程的方法(每种方法都有自己适用的场景)

  • fock twice。使用场景:一个进程要创建一个进程,两个进程同时处理任务,谁也不耽误谁。如果直接用子进程充当第二个进程的角色,那么问题是这样的:如果父进程处理时间长,子进程处理时间短,那么如果父进程不 wait() 处理的话,子进程就会成为僵尸进程,但如果父进程 wait() 子进程的话,父进程就会阻塞,所有有个方法就是让自己尽快推出,任务让子进程的子进程来处理。
/*
 * Avoid zombie processes by calling fork twice.
 * APUE-2e 程序清单8-5
 */
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
	pid_t pid;
	if( (pid = fork()) < 0 )
	{
		printf("fork error.\n");
		exit(-1);
	}
	else if(pid == 0)	/* first child */
	{
		if( (pid = fork()) < 0 )
		{
			printf("fork error.\n");
			exit(-1);
		}
		else if(pid > 0)
		{
			exit(0);
		}
		
		/* We're the second child; our parent becomes init as soon as our real parent exits. */
		printf("second child, parent pid = %d\n", getppid());
		/* ---------------handle tasks--------------- */
		exit(0);
	}
	
	if(waitpid(pid, NULL, 0) != pid)	/* wait for first child */
	{
		printf("waitpid error.\n");
		exit(1);
	}
	printf("parent, first child pid = %d\n", pid);
	/* ---------------handle tasks--------------- */
	
	exit(0);
}
  • 父进程调用wait()方法,但是会使父进程阻塞
  • signal(sigchld,sig_ign),并不是所有系统都兼容。通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收,即可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
  • sigaction+sa_nocldwait,并不是所有系统兼容
  • 在signal handler函数中调用 waitpid (下面的例子能说明用 waitpid 而不用 wait的原因:我们必须指定WOHOANG option——这告诉waitpid不要阻止是否有正在运行的子节点),这样父进程不用阻塞。适用场景: one-request-one-process 的网络服务器程序
  • 让僵尸进程变成孤儿进程,由init回收,就是让父亲先死

void sig_chld(int signo)
{
	pid_t	pid;
	int stat;
	
	while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0 )
	{
		printf("child %d terminated\n", pid);
	}
	return;

}

建立信号处理程序并从该处理程序调用wait不足以防止僵尸。 问题是如果在执行信号处理程序之前生成五个信号,则信号处理程序只执行一次,因为Unix信号通常不排队(这种情况下只会产生一次“SIGCHLD”信号)。在这种情况下,信号处理程序执行一次,留下四个僵尸。故正确的解决方案是调用waitpid而不是wait,并且我们必须指定WOHOANGoption:这告诉waitpid不要阻止是否有正在运行的子节点还没有终止。

wait与waitpid的区别

pid_t wait(int *status);

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

  • wait

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

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:pid = wait(NULL);

返回值:

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

  • waitpid

从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。

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

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

options: options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,如果我们不想使用它们,也可以把options设为0。

返回值——waitpid的返回值比wait稍微复杂一些,一共有3种情况:  

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

wait与waitpid的区别:waitpid提供了wait不能实现的功能

  1. waitpid等待特定的子进程, 而wait则返回任一终止状态的子进程; 
  2. waitpid提供了一个wait的非阻塞版本; 
  3. waitpid支持作业控制(以WUNTRACED选项). 用于检查wait和waitpid两个函数返回终止状态的宏: 这两个函数返回的子进程状态都保存在status指针中, 用以下3个宏可以检查该状态: 
  • WIFEXITED(status):  若为正常终止, 则为真. 此时可执行 WEXITSTATUS(status): 取子进程传送给exit或_exit参数的低8位. 
  • WIFSIGNALED(status):  若为异常终止, 则为真.此时可执行 WTERMSIG(status): 取使子进程终止的信号编号
  • WIFSTOPPED(status): 若为当前暂停子进程, 则为真. 此时可执行 WSTOPSIG(status): 取使子进程暂停的信号编号

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章