Fork me on GitHub
Suzf  Blog

Linux 下 僵尸进程与D进程

 1 . 关于ZOMBIE进程

这些进程已经死亡,但没有释放系统资源,包括内存和一些一些系统表等, 如果这样的进程很多,会引发系统问题。用ps -el看出的进程状态如果是Z,就是僵尸进程。

Z 之所以杀不死,是因为它已经死了,否则怎么叫 Zombie(僵尸)呢?冤魂不散,自然是生前有结未解之故。在UNIX/Linux中,每个进程都有一个父进程,进程号叫PID(Process ID),相应地,父进程号就叫PPID(Parent PID)。当进程死亡时,它会自动关闭已打开的文件,舍弃已占用的内存、交换空间等等系统资源,然后向其父进程返回一个退出状态值,报告死讯。如果程序有 bug,就会在这最后一步出问题。儿子说我死了,老子却没听见,没有及时收棺入殓,儿子便成了僵尸。在UNIX/Linux中消灭僵尸的手段比较残忍,执 行 ps axjf 找出僵尸进程的父进程号(PPID,第一列),先杀其父,然后再由进程天子 init(其PID为1,PPID为0)来一起收拾父子僵尸,超度亡魂,往生极乐。注意,子进程变成僵尸只是碍眼而已,并不碍事,如果僵尸的父进程当前有 要务在身,则千万不可贸然杀之。

清除ZOMBIE(僵尸)进程可以使用如下方法:
1> kill –18 PPID (PPID是其父进程)
这个信号是告诉父进程,该子进程已经死亡了,请收回分配给他的资源。
2>如 果不行则看能否终止其父进程(如果其父进程不需要的话)。先看其父进程又无其他子进程,如果有,可能需要先kill其他子进程,也就是兄弟进程。方法是:
kill –15 PID1 PID2(PID1,PID2是僵尸进程的父进程的其它子进程)。
然后再kill父进程:kill –15 PPID
这 样僵尸进程就可能被完全杀掉了。

ps alx | grep 17191
lsof   -p 进程ID
pstree -p 进程ID
ps     -p 进程ID

详细信息查看:
/proc/[PID]/stat

# cat /proc/6873/stat
6873 (a.out) R 6723 6873 6723 34819 6873 8388608 77 0 0 0 41958 31 0 0 25 0 3 0 5882654 1409024 56 4294967295 134512640 134513720 3215579040 0 2097798 0 0 0 0 0 0 0 17 0 0 0 [[email protected] ~]#

每个参数意思为:
参数 解释
pid=6873 进程(包括轻量级进程,即线程)号
comm=a.out 应用程序或命令的名字
task_state=R 任务的状态,R:runnign, S:sleeping (TASK_INTERRUPTIBLE), D:disk sleep (TASK_UNINTERRUPTIBLE), T: stopped, T:tracing stop,Z:zombie, X:dead
ppid=6723 父进程ID
pgid=6873 线程组号
sid=6723 c该任务所在的会话组ID
tty_nr=34819(pts/3) 该任务的tty终端的设备号,INT(34817/256)=主设备号,(34817-主设备号)=次设备号
tty_pgrp=6873 终端的进程组号,当前运行在该任务所在终端的前台任务(包括shell 应用程序)的PID。
task->flags=8388608 进程标志位,查看该任务的特性
min_flt=77 该任务不需要从硬盘拷数据而发生的缺页(次缺页)的次数
cmin_flt=0 累计的该任务的所有的waited-for进程曾经发生的次缺页的次数目
maj_flt=0 该任务需要从硬盘拷数据而发生的缺页(主缺页)的次数
cmaj_flt=0 累计的该任务的所有的waited-for进程曾经发生的主缺页的次数目
utime=1587 该任务在用户态运行的时间,单位为jiffies
stime=1 该任务在核心态运行的时间,单位为jiffies
cutime=0 累计的该任务的所有的waited-for进程曾经在用户态运行的时间,单位为jiffies
cstime=0 累计的该任务的所有的waited-for进程曾经在核心态运行的时间,单位为jiffies
priority=25 任务的动态优先级
nice=0 任务的静态优先级
num_threads=3 该任务所在的线程组里线程的个数
it_realvalue=0 由于计时间隔导致的下一个 SIGALRM 发送进程的时延,以 jiffy 为单位.
start_time=5882654 该任务启动的时间,单位为jiffies
vsize=1409024(page) 该任务的虚拟地址空间大小
rss=56(page) 该任务当前驻留物理地址空间的大小
Number of pages the process has in real memory,minu 3 for administrative purpose.
这些页可能用于代码,数据和栈。
rlim=4294967295(bytes) 该任务能驻留物理地址空间的最大值
start_code=134512640 该任务在虚拟地址空间的代码段的起始地址
end_code=134513720 该任务在虚拟地址空间的代码段的结束地址
start_stack=3215579040 该任务在虚拟地址空间的栈的结束地址
kstkesp=0 esp(32 位堆栈指针) 的当前值, 与在进程的内核堆栈页得到的一致.
kstkeip=2097798 指向将要执行的指令的指针, EIP(32 位指令指针)的当前值.
pendingsig=0 待处理信号的位图,记录发送给进程的普通信号
block_sig=0 阻塞信号的位图
sigign=0 忽略的信号的位图
sigcatch=082985 被俘获的信号的位图
wchan=0 如果该进程是睡眠状态,该值给出调度的调用点
nswap 被swapped的页数,当前没用
cnswap 所有子进程被swapped的页数的和,当前没用
exit_signal=17 该进程结束时,向父进程所发送的信号
task_cpu(task)=0 运行在哪个CPU上
task_rt_priority=0 实时进程的相对优先级别
task_policy=0 进程的调度策略,0=非实时进程,1=FIFO实时进程;2=RR实时进程

这类进程是有问题的. 需要回收资源.
在unix程序设计的一本书里印像有一种情况会出现这样的进程.
主程序fork出的子进程结束之后. 父进程没有释放子进程所占用的资源. 这时这个进程就一直是zombie状态.

---- 百科:
ZOMBIE:僵尸状态,表示进程结束但尚未消亡的一种状态,此时进程已经结束运行并释放大部分资源,但尚未释放进程控制块。
与ZOMBIE对应的进程状态还有RUNNING(正在运行或等待运行状态),UNINTERRUPTABLE(不可中断阻塞状态),INTERRUPTABLE(可中断阻塞状态),STOPPED(挂起状态)。

服务器通常都会带来一些僵尸进程,占用系统资源,浪费资源等,一般我们使用top命令就可以看得出来,如图:
(可以看到,我的服务器已经产生了两个僵尸进程)

解决方法:

#ps -A -o stat,ppid,pid,cmd |grep -e "^[Zz]"    //先查看具体进程:
#lsof -p ppid;
#lsof -p pid;
#kill -9 pid号         //杀死z进程(这些动作是比较危险的,希望在真正的服务器上面慎用!!!)

假若你的z进程比较多,可以编写个小小的脚本,下面是参与网上的
#ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]' | awk '{print $2}' | xargs kill

首先,我们可以用top命令来查看服务器当前是否有僵尸进程,可以看到第二行行尾有个 0 zombie,如果数字大于0,那么意味着服务器当前存在有僵尸进程  可以用ps和grep命令寻找僵尸进程

ps -A -ostat,ppid,pid,cmd | grep -e '^[Zz]'

命令选项说明:

-A 参数列出所有进程
-o 自定义输出字段 我们设定显示字段为 stat(状态), ppid(进程父id), pid(进程id),cmd(命令)这四个参数
因为状态为 z或者Z的进程为僵尸进程,所以我们使用grep抓取stat状态为zZ进程

运行结果参考如下
Z 12334 12339 /path/cmd
这时,我们可以使用 kill -HUP 12339来杀掉这个僵尸进程
运行后,可以再次运行ps -A -ostat,ppid,pid,cmd | grep -e '^[Zz]'来确认是否将僵尸进程杀死
如果kill 子进程的无效,可以尝试kill 其父进程来解决问题,例如上面例子父进程pid是 12334,那么我们就运行
kill -HUP 12334来解决问题

----
1) 检查当前僵尸进程信息

# ps -ef | grep defunct | grep -v grep | wc -l

175

# top | head -2

top - 15:05:54 up 97 days, 23:49, 4 users, load average: 0.66, 0.45, 0.39

Tasks: 829 total, 1 running, 479 sleeping, 174 stopped, 175 zombie

# ps -ef | grep defunct | grep -v grep

2) 获得杀僵尸进程语句

# ps -ef | grep defunct | grep -v grep | awk '{print "kill -9 " $2,$3}'

执行上面获得的语句即可, 使用信号量9, 僵尸进程数会大大减少.

3) 过一会儿检查当前僵尸进程信息

# ps -ef | grep defunct | grep -v grep | wc -l

125

# top | head -2

top - 15:29:26 up 98 days, 12 min, 7 users, load average: 0.27, 0.54, 0.56

Tasks: 632 total, 1 running, 381 sleeping, 125 stopped, 125 zombie

发现僵尸进程数减少了一些, 但还有不少啊.

4) 再次获得杀僵尸进程语句

# ps -ef | grep defunct | grep -v grep | awk '{print "kill -18 " $3}'

执行上面获得的语句即可, 这次使用信号量18杀其父进程, 僵尸进程应该会全部消失.

5) 过一会儿再检查当前僵尸进程信息

# ps -ef | grep defunct | grep -v grep | wc -l

0

# top | head -2

top - 15:39:46 up 98 days, 23 min, 7 users, load average: 5.46, 2.20, 1.12

Tasks: 134 total, 1 running, 133 sleeping, 0 stopped, 0 zombie

6) 清除ZOMBIE(僵尸)进程原理

# kill -18 PPID

PPID是其父进程, 这个信号是告诉父进程, 该子进程已经死亡了, 请收回分配给他的资源. 如果还不行则看先看其父进程又无其他子进程, 如果有, 可能需要先kill其他子进程, 也就是兄弟进程.

方法是:

# kill -15 PID1 PID2

PID1,PID2是僵尸进程的父进程的其它子进程.

然后再kill父进程:

# kill -15 PPID

 

2 . Linux进程的Uninterruptible sleep(D)状态

 

D,往往是由于 I/O 资源得不到满足,而引发等待,在内核源码 fs/proc/array.c 里,其文字定义为“ "D (disk sleep)", /* 2 */ ”(由此可知 D 原是Disk的打头字母),对应着 include/linux/sched.h 里的“ #define TASK_UNINTERRUPTIBLE 2 ”。举个例子,当 NFS 服务端关闭之时,若未事先 umount 相关目录,在 NFS 客户端执行 df 就会挂住整个登录会话,按 Ctrl+C 、Ctrl+Z 都无济于事。断开连接再登录,执行 ps axf 则看到刚才的 df 进程状态位已变成了 D ,kill -9 无法杀灭。正确的处理方式,是马上恢复 NFS 服务端,再度提供服务,刚才挂起的 df 进程发现了其苦苦等待的资源,便完成任务,自动消亡。若 NFS 服务端无法恢复服务,在 reboot 之前也应将 /etc/mtab 里的相关 NFS mount 项删除,以免 reboot 过程例行调用 netfs stop 时再次发生等待资源,导致系统重启过程挂起。

运行在KVM虚拟机里的一些进程突然出了问题,这些出了问题的进程无法用kill杀掉,使用ps可以看到这些进程处于D状 态:

# ps -a -ubuild -o pid,ppid,stat,command

PID  PPID STAT COMMAND

17009     1 Ds   -bash

17065     1 D    ls --color=tty -al

17577     1 D    /usr/java/jdk1.5.0_17/bin/java -Xmx512m -classpath /usr/local/a

17629     1 D    /usr/java/jdk1.5.0_17/bin/java -Xmx512m -classpath /usr/local/a

ps 的手册里说D状态是uninterruptible sleep,Linux进程有两种睡眠状态,一种interruptible sleep,处在这种睡眠状态的进程是可以通过给它发信号来唤醒的,比如发HUP信号给nginx的master进程可以让nginx重新加载配置文件而 不需要重新启动nginx进程;另外一种睡眠状态是uninterruptible sleep,处在这种状态的进程不接受外来的任何信号,这也是为什么之前我无法用kill杀掉这些处于D状态的进程,无论是”kill”, “kill -9″还是”kill -15″,因为它们压根儿就不受这些信号的支配。

进程为什么会被置于uninterruptible sleep状态呢?处于uninterruptible sleep状态的进程通常是在等待IO,比如磁盘IO,网络IO,其他外设IO,如果进程正在等待的IO在较长的时间内都没有响应,那么就很会不幸地被 ps看到了,同时也就意味着很有可能有IO出了问题,可能是外设本身出了故障,也可能是比如挂载的远程文件系统已经不可访问了,我这里遇到的问题就是由 down掉的NFS服务器引起的。

正是因为得不到IO的相应,进程才进入了uninterruptible sleep状态,所以要想使进程从uninterruptible sleep状态恢复,就得使进程等待的IO恢复,比如如果是因为从远程挂载的NFS卷不可访问导致进程进入uninterruptible sleep状态的,那么可以通过恢复该NFS卷的连接来使进程的IO请求得到满足,除此之外,要想干掉处在D状态进程就只能重启整个Linux系统了。

看到有人说如果要想杀掉D状态的进程,通常可以去杀掉它的父进程(通常是shell,我理解的这种情况是在shell下直接运行的该进程,之后该进 程转入了D状态),于是我就照做了,之后就出现了上面的状态:他们的父进程被杀掉了,但是他们的父进程PID都变成了1,也就是init进程,这下可如何是好?此时我这些D状态的进程已经影响到其他一些进程的运行,而已经无法访问的NFS卷又在段时间内无法恢复,那么,只好重新启动了,root不是玉皇大 帝,也有无奈的时候。