本文主机频道详细介绍了“如何避免linux僵尸进程”。内容详细,步骤清晰,细节处理得当。希望这篇文章《如何避免linux僵尸进程》能帮你解决疑惑。让我们按照主机频道的思路,一起学习新知识。
Linux僵尸进程是一个很长的死进程,但它仍然在进程表中占有一席之地;如果父进程在子进程死亡时没有wait(),通常可以被ps看做是“”,从而产生僵尸进程;如果产生了大量的僵尸进程,由于没有可用的进程号,系统不会产生新的进程,所以需要避免僵尸进程。
1.什么是僵尸进程?在UNIX系统中,一个进程结束了,但是他的父进程并没有为他等待(调用wait/waitpid),所以他会成为一个僵尸进程。当用ps命令观察进程的执行状态时,可以看到这些进程的状态栏已经失效。僵尸进程是一个长时间的死进程,但是它仍然在processs表中占据一个位置。
但是如果进程的父进程已经先结束了,那么这个进程就不会变成僵尸进程。因为在每个进程结束时,系统会扫描当前系统中运行的所有进程,看是否有进程是刚刚结束的进程的子进程。如果是这样,Init进程将接管并成为他的父进程,从而确保每个进程都有一个父进程。Init进程会自动等待其子进程,所以所有被Init接管的进程都不会变成僵尸进程。
二、UNIX下进程的运行方式每个Unix进程在进程表中都有一个入口点,核心进程在执行进程时使用的所有信息都存储在入口点中。当您使用ps命令查看系统中的进程信息时,您会在进程表中看到相关数据。当fork()系统调用建立一个新进程时,核心进程会在进程表中为这个新进程分配一个入口点,然后将相关信息存储在入口点对应的进程表中。这些消息之一是其父进程的标识代码。
子进程的结束和父进程的操作是异步的,即父进程永远无法预测子进程何时结束。那么父进程会不会因为太忙而没有时间等待子进程,或者不知道子进程什么时候结束,而在子进程结束的时候丢失了状态信息?
不会。因为UNIX提供了一种机制,确保只要父进程想知道子进程在最后的状态信息,就可以获得。这个机制是,当子进程已经结束生命周期时,它会执行exit()系统调用,内核会释放进程的所有资源,包括打开的文件和占用的内存。但是,某些信息(包括进程ID、退出代码、进程的退出状态、进程占用的CPU时间量的运行时间等。)仍然是为它保留的,这些数据会一直保留,直到系统把它传递给它的父进程,直到父进程通过wait/waitpid把它拿起来才释放。
换句话说,当一个进程死亡时,它并没有完全消失。该进程被终止,不再运行,但仍有一些剩余数据等待父进程恢复。当父进程fork()有子进程时,它必须用wait()(或waitpid())等待子进程退出。正是这个wait()动作让子进程的残留数据消失了。
三、僵尸进程的危害如果父进程不调用wait/waitpid,那么预留的信息不会被释放,它的进程号会一直被占用,但是系统的进程表的容量是有限的,可以使用的进程数也是有限的。如果生成了大量的僵尸进程,系统将无法生成新的进程,因为没有可用的进程号。
因此,失效的进程不仅会占用系统的内存资源,影响系统的性能,如果数量过大,还会导致系统瘫痪。而且因为调度器不能选择失效的进程,所以kill命令也不能用来删除失效的进程,唯一的办法就是重启系统。
四、僵尸进程的出现如果父进程在子进程死亡时没有wait(),通常可以被ps看到显示为“
可以看出,失效进程出现在子进程终止之后,但在父进程读取数据之前。利用这一点,我们可以用下面的程序设置一个失效的进程:
# include & ltstdio.h & gt# include & ltsys/types . h & gt;main() { if(!fork()) { printf("child pid=%d\n ",getpid());退出(0);}睡眠(20);printf("parent pid=%d \n ",getpid());退出(0);当上述程序在后台模式下执行时,第17行强制程序休眠20秒,让用户有时间输入ps -e指令并观察进程的状态。我们看到失效的流程出现在流程表中。当父进程的执行被终止时,我们会发现失效的进程随着ps -e命令一起消失了。这是因为父进程终止后,init进程会接管父进程留下的这些孤儿进程,这些孤儿进程执行后,它们在进程表中的入口点会被删除。如果一个程序在设计上有缺陷,就可能导致一个进程的父进程一直处于休眠状态或者陷入死循环。父进程没有等待子进程,也没有被终止以使init接管。子进程执行后,将成为一个失效的进程,这个失效的进程可能会一直留在系统中,直到系统重新启动。
看一个僵尸进程的例子。
子进程要执行的程序test_prog。
//test . c # include & lt;stdio.h & gtint main(){ int I = 0;for(I = 0;我& lt10;i++){ printf(& quot;子时间% d \ n & quot,I+1);睡眠(1);}返回0;}父流程father.c的代码
# include & ltstdio.h & gt
# include & ltunistd.h & gt
# include & ltsys/types . h & gt;
# include & ltsys/wait . h & gt;
int main()
{
int PID = fork();
如果(pid == 0)
{
系统(& quot。/test_prog");
_ exit(0);
}否则
{
int I = 0;
/*
int status = 0;
而(!waitpid(PID & amp;地位,WNOHANG))
{
printf(& quot;父亲正在等待% d \ n & quot,++ I);
睡眠(1);
}*/
while (1)
{
printf(& quot;父亲等待了% d \ n & quot,++ I);
睡眠(1);
}
返回0;
}
}
执行。/父亲,当子进程退出时,会因为父进程不注意它的退出而出现僵尸进程。
20786 pts/0 00:00:00父亲20787 pts/0 00:00:00父亲& lt已不存在的& gt摘要:除非父进程忽略SIGCLD,否则子进程在父进程wait()之前会失效。此外,没有wait()就死亡的父进程的子进程(活动的或失效的)(仍然假设父进程没有忽略SIGCLD)成为init的子进程,init开始处理它们。
动词 (verb的缩写)如何避免僵尸进程1?父进程通过wait和waitpid等函数等待子进程结束,这将导致父进程挂起。
在最后一个例子中,如果我们稍作修改,在第8行的sleep()系统调用之前执行wait()或waitpid()系统调用,终止后子进程会立即将其进程表中的数据返回给父进程,系统会立即删除入口点。在这种情况下,将不会生成失效的流程。
2.如果父进程很忙,可以使用signal函数来安装SIGCHLD的处理程序。子进程结束后,父进程将收到这个信号,并可以在处理程序中调用等待回收。
3.如果父进程不在乎子进程什么时候结束,你可以用signal(SIGCLD,SIG_IGN)或者signal(SIGCHLD,SIG_IGN)通知内核你对子进程的结束不感兴趣,那么内核会在子进程结束后将其回收,不再向父进程发送信号。
4.分支两次,父进程分支一个子进程,然后继续工作。子进程在一个孙子进程后分叉退出,所以孙子进程由init接管,孙子进程结束后,init会回收。但是,子进程的恢复必须自己完成。下面是Stevens通过使用两个folk来避免僵尸进程的一个例子:
#包含& quotapue.h & quot# include & ltsys/wait . h & gt;int main(void)...{ pid _ t pidif((PID = fork())& lt;0) ...{ err _ sys(& quot;分叉错误& quot);} else if (pid == 0)...{ /**//*第一个孩子*/if((PID = fork())& lt;0) err_sys("分叉错误& quot);else if(PID & gt;0)退出(0);/**//*第二个分支的父级==第一个子级*//* *//* * We & # 39;re二胎;当我们真正的父调用上面语句中的exit()时,我们的父就变成init。*这里& # 39;我们在哪里& # 39;继续执行,知道当*我们& # 39;重新完成后,init将获取我们的状态。*/睡眠(2);printf(& quot;第二胎,父pid = % d & quot,getppid());退出(0);} if (waitpid(pid,NULL,0)!= pid) /**//*等待第一个孩子*/err _ sys(& quot;waitpid错误);/**//* *我们& # 39;re父进程(原始进程);我们继续执行,*知道我们& # 39;你不是第二个孩子的父母。*/退出(0);}
评论前必须登录!
注册