参考这篇文章:
http://www.mike.org.cn/articles/treatment-of-zombie-processes-under-linux/
在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸。
如果他的父进程没安装SIGCHLD信号处理函数,也没有调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。
僵尸进程的避免
1、父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起
2、如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收
3、如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号
4、还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。
子进程结束后为什么要进入僵尸状态?
因为父进程可能要取得子进程的退出状态等信息。
S(state of the process )
O:进程正在处理器运行
S:休眠状态(sleeping)
R:等待运行(runable)
I:空闲状态(idle)
Z:僵尸状态(zombie)
T:跟踪状态(Traced)
B:进程正在等待更多的内存页
C:cpu利用率的估算值(cpu usage)
SIGCHLD信号(17),该信号的默认处理动作是忽略
事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。请编写程序验证这样做不会产生僵尸进程。
kill -18 PPID (SIGCONT, PPID是其父进程)
这个信号是告诉父进程,该子进程已经死亡了,请收回分配给他的资源。(没实验过,不确定)
SIGCONT也是一个有意思的信号。如前所述,当进程停止的时候,这个信号用来告诉进程恢复运行。该信号的有趣的地方在于:它不能被忽略或阻塞,但可以被捕获。缺省行为是丢弃该信号。
3. kill默认发的是 SIGTERM,也就是15.
全部信号的列表如下:
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1
36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5
40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9
44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13
52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9
56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5
60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1
64) SIGRTMAX
各种信号的处理方式可以参考这个网页:http://blog.chinaunix.net/uid-11848011-id-96416.html
对信号的三种处理方式
1、忽略此信号:大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILL
和 SIGSTOP
。这两种信号不能被忽略的,原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问或除以0),则进程的行为是示定义的。
2、直接执行进程对于该信号的默认动作
3、捕捉信号:执行自定义动作(使用signal
函数),为了做到这一点要通知内核在某种信号发生时,调用一个用户函数handler
。在用户函数中,可执行用户希望对这种事件进行的处理。注意,不能捕捉SIGKILL
和SIGSTOP
信号。
Ctrl-c产生SIGINT信号,Ctrl-产生SIGQUIT信号,Ctrl-z产生SIGTSTP信号
内核实现信号捕捉的步骤:
1、用户为某信号注册一个信号处理函数sighandler
。
2、当前正在执行主程序,这时候因为中断、异常或系统调用进入内核态。
3、在处理完异常要返回用户态的主程序之前,检查到有信号未处理,并发现该信号需要按照用户自定义的函数来处理。
4、内核决定返回用户态执行sighandler
函数,而不是恢复main
函数的上下文继续执行!(sighandler
和main
函数使用的是不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程)
5、sighandler
函数返回后,执行特殊的系统调用sigreturn
从用户态回到内核态
6、检查是否还有其它信号需要递达,如果没有 则返回用户态并恢复主程序的上下文信息继续执行。