僵尸进程
产生原因
僵尸进程,这是多进程应用很容易碰到的问题。正常情况下,当一个进程创建了子进程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源;而子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。如果父进程没这么做,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。换句话说,父亲应该一直对儿子负责,善始善终,如果不作为或者跟不上,都会导致“问题少年”的出现。
僵尸进程的影响
一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建,所以这种情况一定要避免。
查找僵尸进程的命令
$ ps aux | grep Z
#或者
$ ps -ef | grep defunct | grep -v grep
如何处理僵尸进程
信号来杀死进程,但是僵尸进程已经死了, 你不能杀死已经死掉的东西。 因此你需要输入的命令应该是
kill -s SIGCHLD pid
# 将这里的 pid 替换成父进程的进程 id,这样父进程就会删除所有以及完成并死掉的子进程了。有时这样是删除不掉僵尸进程
僵尸进程的产生是因为父进程没有 wait() 子进程。所以如果我们自己写程序的话一定要在父进程中通过 wait() 来避免僵尸进程的产生。当系统中出现了僵尸进程时,我们是无法通过 kill 命令把它清除掉的。但是我们可以杀死它的父进程,让它变成孤儿进程,并进一步被系统中管理孤儿进程的进程收养并清理。
如果是容器中产生僵尸进程,对容器重启就可以解决
python模拟僵尸进程的产生
python代码如下
#!/usr/bin/python
# -*- coding: utf-8 -*
import os,sys,time
pid = os.fork()
getpid = os.getpid()
getppid = os.getppid()
if pid == 0:
print "getpid=%d,getppid=%d,该路径为子进程,产生的子进程ID为%d" %(getpid,getppid,pid)
else:
print "getpid=%d,getppid=%d,该路径为父进程,产生的子进程ID为%d" %(getpid,getppid,pid)
time.sleep(100)
使用os.fork()之后,子进程是父进程的复制品,在内存中会把父进程的代码及内存分配情况拷贝一份生成子进程的运行空间,这样子进程与父进程的所有代码都一样,两个进程之间的运行时独立的,互不影响。在父进程中获取到的pid是子进程的pid号,在子进程中获取的pid是0.
fork()函数被调用一次,返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID
在执行上面这段代码的时候,会有两个独立的运行空间独立的代码来执行,在父进程中执行的时候,由于返回的是子进程的pid,所以不会等于0,就走else。在子进程的独立的运行空间执行上面的那份代码,因为fork给子进程返回的是0,所以就走if那条。
终端1:
$ python zone.py
getpid=3425,getppid=1861,该路径为父进程,产生的子进程ID为3426
getpid=3426,getppid=3425,该路径为子进程,产生的子进程ID为0
终端2:
$ ps -ef | grep defunct | grep -v grep
root 3426 3425 0 17:02 pts/0 00:00:00 [python] <defunct>
$ pstree -p
systemd(1)─┬
├─sshd(1217)───sshd(1859)─┬─bash(1861)───python(3425)───python(3426)
│ └─bash(2828)───pstree(3439)
$ kill -s SIGCHLD 3425 #通知父进程回收僵尸,但是无效,因为父进程在睡眠
$ ps -ef | grep defunct | grep -v grep
root 3426 3425 0 17:02 pts/0 00:00:00 [python] <defunct>
$ kill -9 3425 #杀死僵尸进程的父进程,子进程变成孤儿进程从而被回收
$ ps -ef | grep defunct | grep -v grep
#僵尸进程消失
子进程成了僵尸进程是因为子进程结束的时候,父进程还在睡觉,不能调用wait()或waitpid()去获取子进程的终止状态,但是等父进程醒来的时候,就会把僵尸进程给处理掉