僵尸进程是当
子进程比
父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被
init接管,子进程退出后init会回收其占用的相关资源
基本概念
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁, 而是留下一个称为僵尸进程(Zombie)的
数据结构(
系统调用exit,它的作用是 使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)
危害
由于
子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 那么会不会因为父进程太忙来不及wait子进程,或者说不知道 子进程什么时候结束,而丢失子进程结束时的
状态信息呢? 不会。因为UNⅨ提供了一种机制可以保证只要
父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,
运行时间the amount of CPU time taken by the process等)。直到
父进程通过wait /
waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
僵尸进程的避免
避免示例
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
进程处理
它需要它的父进程来为它收尸,如果它的父进程没安装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也不能杀死僵尸进程。
补救办法
产生过程
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有
内存空间,没有任何
可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么
init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。
查看方法
查看僵尸进程,利用命令ps,可以看到有标记为Z的进程就是僵尸进程。
避免方法
具体步骤
1)在
SVR4中,如果调用
signal或
sigset将
SIGCHLD的配置设置为忽略,则不会产生僵死子进程。另外,使用SVR4版的sigaction,则可设置SA_NOCLDWAIT标志以避免子进程僵死。
Linux中也可使用这个,在一个程序的开始调用这个函数
2)调用
fork两次。程序8 - 5 实现了这一点。
3)用waitpid等待子进程返回.
===========================================
zombie进程是
僵死进程。防止它的办法,一是用wait,waitpid之类的函数获得
进程的终止状态,以释放资源。另一个是fork两次
===========================================
defunct进程只是在process table里还有一个记录,其他的资源没有占用,除非你的系统的process个数的限制已经快超过了,zombie进程不会有更多的坏处。
可能的方法就是
reboot系统可以消除zombie进程。
===========================================
任何程序都有僵尸状态,它占用一点内存资源(也就是进程表里还有一个记录),仅仅是表象而已不必害怕。如果程序有问题
有机会遇见,解决大批量僵尸简单有效的办法是重起。kill是无任何效果的
在Unix下的一些进程的运作方式。当一个进程死亡时,它并不是完全的消失了。进程终止,它不再运行,但是还有一些残留的
小东西等待
父进程收回。这些残留的东西包括子进程的
返回值和其他的一些东西。当父进程fork() 一个子进程后,它必须用 wait() 或者 waitpid() 等待子进程退出。正是这个 wait() 动作来让子进程的
残留物消失。
自然的,在上述规则之外有个例外:父进程可以忽略 SIGCLD
软中断而不必要 wai()。可以这样做到(在支持它的系统上,比如Linux):
当前子进程死亡时
父进程没有 wait(),通常用 ps 可以看到它被显示为“”。它将永远保持这样 直到父进程wait(),或者按以下方法处理。
这里是你必须知道的另一个规则:当父进程在它wait()子进程之前死亡了(假定它没有忽略 SIGCLD),子进程将把 init(pid 1)进程作为它的父进程。如果子进程工作得很好并能够控制,这并不是问题。但如果子进程已经是 defunct,我们就有了一点小麻烦。看,原先的父进程不可能再 wait(),因为它已经消亡了。这样,init 怎么知道 wait() 这些 zombie 进程。
答案:不可预料的。在一些系统上,init周期性的破坏掉它所有的defunct进程。在另外一些系统中,它干脆拒绝成为任何defunct进程的
父进程,而是马上毁灭它们。如果你使用上述系统的一种,可以写一个简单的循环,用属于init的defunct进程填满进程表。这大概不会令你的
系统管理员很高兴吧?
你的任务:确定你的父进程不要忽略 SIGCLD,也不要wait() 它fork() 的所有进程。不过,你也未必要总是这样做(比如,你要起一个
daemon或是别的什么东西),但是你必须小心编程,如果你是一个fork() 的新手。另外,也不要在心理上有任何束缚。
进程清除
改写父进程
具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。就是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,而此时,尽管对它的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
把父进程杀掉
父进程死后,僵尸进程成为“
孤儿进程”,过继给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 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)。
小结
子进程成为 defunct 直到
父进程wait(),除非父进程忽略了 SIGCLD。
更进一步,父进程没有 wait() 就消亡(仍假设父进程没有忽略 SIGCLD)的子进程(活动的或者 defunct)成为 init 的子进程,init 用重手法处理它们。