殺不掉的僵屍(zombie)進程
Linux的進程,有以下幾種狀態(摘自本文):
State
Description
D
Uninterruptible sleep (usually IO)
R
Running or runnable (on run queue)
S
Interruptible sleep (waiting for an event to complete)
T
Stopped, either by a job control signal or because it is being traced.
W
Paging (not valid since the 2.6.xx kernel)
X
Dead (should never be seen)
Z
Defunct ("zombie") process, terminated but not reaped by its parent.
當一個進程處於Z狀態,我們稱之為zombie進程,如下所示:
#top -b -p 56249
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
56249 ats 20 0 0 0 0 Z 0.0 0.0 5914:36 [ET_NET 0] <defunct>
正常情況下,處於zombie狀態的進程,會很快地被它的父進程回收,以致於我們根本不會注意到zombie進程的存在。可在實踐過程中,卻有一些無法使用kill -9命令殺掉的zombie進程,這常常令我們束手無策。
如果某個進程一直處於zombie狀態,可能會帶來一些嚴重的問題,例如,假設這個進程沒有正確地close掉socket,就會導致這些socket處於close_wait狀態,這些socket將會占用系統的ip/port資源,將導致其他程序無法創建特定socket。
當出現「殺不掉」的zombie進程,我們常常歸咎於kernel的bug,不了了之,但其實還有一種情況常常被忽略。讓我們看看上面的這個zombie進程內部,是否還有其他線程(使用top命令的-H參數):
#top -b -H -p 56249
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
56249 ats 20 0 0 0 0 Z 0.0 0.0 253:54.05 [ET_NET 0] <defunct>
56337 ats 20 0 0 0 0 D 0.0 0.0 38:53.67 [ET_AIO 0]
56338 ats 20 0 0 0 0 D 0.0 0.0 38:48.24 [ET_AIO 1]
由上可見,雖然[ET_NET 0](pid=56249)進程處於zombie狀態,但它其實是一個多線程的程序,該程序中的其他線程,如[ET_AIO 0](pid=56337)等,處於D(Uninterruptible sleep)狀態,因為D狀態的進程(在Linux中,線程只是特殊的進程)無法被中斷,因此kill -9無法殺掉D狀態的進程。也正因為這些D狀態的進程的存在,導致父進程無法順利的回收它們。
通常,我們還需要分析處於D狀態的進程卡在了哪裡。可通過/proc文件系統查看D狀態進程的調用棧:
#cat /proc/56337/stack
[<ffffffff811b372e>] __blockdev_direct_IO_newtrunc+0x6fe/0xb90
[<ffffffff811b3c1e>] __blockdev_direct_IO+0x5e/0xd0
[<ffffffff811b1317>] blkdev_direct_IO+0x57/0x60
[<ffffffff81113543>] generic_file_aio_read+0x793/0x870
[<ffffffff81177c3a>] do_sync_read+0xfa/0x140
[<ffffffff81178635>] vfs_read+0xb5/0x1a0
[<ffffffff81178962>] sys_pread64+0x82/0xa0
[<ffffffff8100b0f2>] system_call_fastpath+0x16/0x1b
[<ffffffffffffffff>] 0xffffffffffffffff
由上可見,該進程卡在了磁盤的read操作中,很可能是磁盤壞了。