简介
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被
销毁, 而是留下一个称为僵尸进程(zombie)的数据结构(系统调用exit,它的作用是 使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁) 2. 僵尸进程的危害 由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 那么不会因为父进程太忙来不及wait子进程,或者说不知道 子进程什么时候结束,而丢失子进程结束时的状态信息呢? 不会.因为unix提供了一种机制可以保证 只要父进程想知道子进程结束时的状态信息, 就可以得到. 这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等. 但是仍然为其保留一定的信息(包括进程号the process id,退出状态the termination status of the process,运行时间the amount of cpu time taken by the process等), 直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果你进程不调用wait / waitpid的话, 那么保留的那段信息就不会 释放,其进程号就会一定被占用,但是系统所能使用的进程号是有限的,如果大量的产生 僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免. 3.僵尸进程的避免 1、父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起 2. 如果父进程很忙,那么可以用signal函数为sigchld安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收 3. 如果父进程不关心子进程什么时候结束,那么可以用signal(sigchld, sig_ign) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号 4. 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。 下面就是stevens给的采用两次folk避免僵尸进程的示例.
example recall our discussion in section 8.5 about zombie processes. if we want to write a process so that it forks a child but we don't want to wait for the child to complete and we don't want the child to become a zombie until we terminate, the trick is to call fork twice. the program in figure 8.8 does this. we call sleep in the second child to ensure that the first child terminates before printing the parent process id. after a fork, either the parent or the child can continue executing; we never know which will resume execution first. if we didn't put the second child to sleep, and if it resumed execution after the fork before its parent, the parent process id that it printed would be that of its parent, not process id 1. executing the program in figure 8.8 gives us $ ./a.out $ second child, parent pid = 1 note that the shell prints its prompt when the original process terminates, which is before the second child prints its parent process id. figure 8.8. avoid zombie processes by calling fork twice
#include "apue.h"
#include
int main(void) ...{
pid_t pid;
if ((pid = fork()) < 0)
{
err_sys("fork error");
} else if (pid == 0) { /* first child */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)
exit(0); /* parent from second fork == first child */
/ * we're the second child; our parent becomes init as soon as our real parent calls exit() in the statement above. here's where we'd continue executing, knowing that when we're done, init will reap our status. */
sleep(2);
printf("second child, parent pid = %d ", getppid());
exit(0);
}
if (waitpid(pid, null, 0) != pid) /* wait for first child */
err_sys("waitpid error");
/ * we're the parent (the original process); we continue executing, knowing that we're not the parent of the second child. */
exit(0);
}
3、僵尸进程的处理: 它需要它的父进程来为它收尸,如果他的父进程没安装sigchld信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态; 存在的问题:如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程,系统的性能可能会收到影响。 ** 如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。 4、子进程结束后为什么要进入僵尸状态? * 因为父进程可能要取得子进程的退出状态等信息。 5、僵尸状态是每个子进程比经过的状态吗? 是的。 * 任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(zombie)的数据结构,等待父进程处理。这是每个 子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 * 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。 6、如何查看僵尸进程: $ ps -el 其中,有标记为z的进程就是僵尸进程 s代表休眠状态;d代表不可中断的休眠状态;r代表运行状态;z代表僵死状态;t代表停止或跟踪状态
概 述
在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装sigchld信号处理函数调用waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程,无法正常结束,此时即使是root身份kill -9也不能杀死僵尸进程。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然存在),僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程。
僵尸进程是怎样产生的
一个进程在调用exit命令结束自己的生命的时候,其实
它并没有真正的被销毁,而是留下一个称为僵尸进程(zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在linux进程的状态中,僵尸进程 是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装sigchld信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。怎么查看僵尸进程,利用命令ps,可以看到有标记为z的进程就是僵尸进程。
怎样清理僵尸进程
1.改写父进程,在子进程死后要为它收尸。
具体做法是接管sigchld信号。子进程死后,会发送sigchld信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。就是基于这样的原理:就算父进程没有调用wait,内核也会向它发送sigchld消息,而此时,尽管对它的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
2.把父进程杀掉。
父进程死后,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程.它产生的所有僵尸进程也跟着消失。
===========================================
在linux中可以用 ps auwx 发现僵尸进程
a all w/ tty, including other users 所有窗口和终端,包括其他用户的进程
u user-oriented 面向用户(用户友好)
-w,w wide output 宽格式输出
x processes w/o controlling ttys 在僵尸进程后面 会标注
ps axf 看进程树,以树形方式现实进程列表
ps axm 会把线程列出来,在linux下进程和线程是统一的,是轻量级进程的两种方式。
ps axu 显示进程的详细状态
===========================================
killall kill -15 kill -9 一般都不能杀掉 defunct进程
用了kill -15,kill -9以后 之后反而会多出更多的僵尸进程
kill -kill pid fuser -k pid 可以考虑杀死他的parent process,
kill -9 他的parent process
===========================================
一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程(zombie process)。
避免zombie的方法
具体步骤
1)在svr4中,如果调用signal或sigset将sigchld的配置设置为忽略,则不会产生僵死子进程。另外,使用svr4版的sigaction,则可设置sa_nocldwait标志以避免子进程僵死。
linux中也可使用这个,在一个程序的开始调用这个函数
signal(sigchld,sig_ign);
2)调用fork两次。程序8 - 5 实现了这一点。
3)用waitpid等待子进程返回.
===========================================
zombie进程是僵死进程。防止它的办法,一是用wait,waitpid之类的函数获得
进程的终止状态,以释放资源。另一个是fork两次
===========================================
defunct进程只是在process table里还有一个记录,其他的资源没有占用,除非你的系统的process个数的限制已经快超过了,zombie进程不会有更多的坏处。
可能唯一的方法就是reboot系统可以消除zombie进程。
===========================================
任何程序都有僵尸状态,它占用一点内存资源(也就是进程表里还有一个记录),仅仅是表象而已不必害怕。如果程序有问题有机会遇见,解决大批量僵尸简单有效的办法是重起。kill是无任何效果的
"fork与zombie/defunct"
在unix下的一些进程的运作方式。当一个进程死亡时,它并不是完全的消失了。进程终止,它不再运行,但是还有一些残留的小东西等待父进程收回。这些残留的东西包括子进程的返回值和其他的一些东西。当父进程 fork() 一个子进程后,它必须用 wait() 或者 waitpid() 等待子进程退出。正是这个 wait() 动作来让子进程的残留物消失。
自然的,在上述规则之外有个例外:父进程可以忽略 sigcld 软中断而不必要 wait()。可以这样做到(在支持它的系统上,比如linux):
main()
{
signal(sigcld, sig_ign); /* now i don't have to wait()! */
.
.
fork();
fork();
fork(); /* rabbits, rabbits, rabbits! */
}
现在,子进程死亡时父进程没有 wait(),通常用 ps 可以看到它被显示为“”。它将永远保持这样 直到 父进程 wait(),或者按以下方法处理。
这里是你必须知道的另一个规则:当父进程在它wait()子进程之前死亡了(假定它没有忽略 sigcld),子进程将把 init(pid 1)进程作为它的父进程。如果子进程工作得很好并能够控制,这并不是问题。但如果子进程已经是 defunct,我们就有了一点小麻烦。看,原先的父进程不可能再 wait(),因为它已经消亡了。这样,init 怎么知道 wait() 这些 zombie 进程。
答案:不可预料的。在一些系统上,init周期性的破坏掉它所有的defunct进程。在另外一些系统中,它干脆拒绝成为任何defunct进程的父进程,而是马上毁灭它们。如果你使用上述系统的一种,可以写一个简单的循环,用属于init的defunct进程填满进程表。这大概不会令你的系统管理员很高兴吧?
你的任务:确定你的父进程不要忽略 sigcld,也不要 wait() 它 fork() 的所有进程。不过,你也未必 要 总是这样做(比如,你要起一个 daemon 或是别的什么东西),但是你必须小心编程,如果你是一个 fork() 的新手。另外,也不要在心理上有任何束缚。
总 结
子进程成为 defunct 直到父进程 wait(),除非父进程忽略了 sigcld 。
更进一步,父进程没有 wait() 就消亡(仍假设父进程没有忽略 sigcld )的子进程(活动的或者 defunct)成为 init 的子进程,init 用重手法处理它们。
阅读(922) | 评论(0) | 转发(0) |