在使用 top 命令监控 Linux 系统时,可能会在 '任务 (Tasks)' 行中发现类似于 1 僵尸 (zombie) 的条目。

top - 16:03:19 up 7:09, 1 user, load average: 2.24, 2.21, 2.29
任务: 392 总计, 1 执行, 390 等待, 0 停止, 1 僵尸
...

这个 '僵尸' 是什么,系统会受到怎样的影响呢?在这篇文章中,我们将清晰地解释僵尸进程(Zombie Process)的身份以及如何识别和解决它。


什么是僵尸进程(Zombie Process) 🧟



为了更易理解,可以比喻为进程的生命周期。

  1. 出生 (Fork): 父进程(Parent Process) 创建子进程(Child Process) (fork())。

  2. 执行 (Exec): 子进程执行其工作。

  3. 结束 (Exit): 子进程完成工作并退出 (exit())。

  4. 收割 (Wait): 当子进程结束时,操作系统(内核) 会在进程表中保留该进程的 PID、退出状态等信息,并向父进程发送 SIGCHLD (子进程已结束) 信号。

  5. 父进程接收到此信号后,需调用 wait() 系统调用以 "收获" 子进程的退出状态信息。只有在该信息被收获之后,内核才会从进程表中完全移除子进程的条目。

僵尸进程 正是卡在第 4 和第 5 步之间的状态。也就是说,子进程已经完成执行并退出,但父进程尚未调用 wait() 来收获退出状态

正如其名,这种进程处于死亡状态(不再执行)。因此,不会消耗 CPU 或内存等系统资源。

僵尸进程为何成为问题?

虽然僵尸进程本身几乎不使用系统资源,但它会占用进程表中的一个插槽(PID)。

如果因为父进程的错误等原因,僵尸进程持续堆积且没有清理,那么系统能够分配的 PID 数量(最大值)就可能会达到上限。在这种情况下,系统将无法生成新的进程,可能导致严重故障。top 中显示 1~2 个僵尸进程并不罕见,但如果这个数字持续增加,则需要采取措施。


如何确认和识别僵尸进程

top 命令仅显示僵尸的 _数量_。要查看哪些进程处于僵尸状态以及它们的父进程是谁,则需使用 ps 命令。

最简单的方法是在 ps 命令的 STAT (状态) 列中查找标记为 'Z' 的进程。

# 详细查看系统中的所有进程并过滤出 'Z' 状态(僵尸)
ps -elf | grep ' Z '

# 或使用 'aux' 选项 (第 8 列($8)为状态(STAT))
ps aux | awk '$8=="Z"'

示例输出 :

# ps -elf | grep ' Z '
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
0 Z  user  5021  5000  0  80   0 -     0 exit   15:30 ?        00:00:00 [defunct]

上述示例中,重要信息如下:

  • S (状态): Z (表示处于僵尸状态)

  • PID: 5021 (这是僵尸进程的 PID)

  • PPID: 5000 (这是未收获僵尸的父进程的 PID)

  • CMD: [defunct] (表示已退出但尚未清理的名称)


解决僵尸进程的方法



最重要的事实是僵尸进程无法通过 kill 命令终止

kill -9 5021 (以上示例的僵尸 PID)

上面的命令不会生效。因为僵尸已经处于 "死去的" 状态,无法处理 kill 信号。

解决僵尸进程的唯一方法是让父进程调用 wait()

步骤 1: 发送信号给父进程 (推荐)

首先尝试的方法是向父进程(PPID)手动发送 SIGCHLD 信号以检查子进程的状态。

# 向以上示例的父 PID(5000)发送 SIGCHLD 信号
kill -s SIGCHLD 5000

这会让父进程知道 "你的某个子进程已经结束,请检查一下!"。如果正常编程的父进程接收到此信号,它将收获并清理僵尸。

步骤 2: 强制结束父进程 (最后手段)

如果步骤 1 不奏效,则说明父进程(PPID 5000) 本身已停止,或 wait() 调用的逻辑存在严重错误。

在这种情况下,强制结束父进程 是唯一的解决方法。

# 结束父进程(PPID 5000)
kill 5000

# 若仍未结束则强制结束
kill -9 5000

为什么结束父进程能够解决问题?

在 Linux 中,如果父进程死亡,它的子进程(孤儿进程)会被init 进程(PID 1) 或 systemd 领养。init 进程会定期检查子进程的状态,并立即收获终止的子进程(包括僵尸)。

因此,当麻烦的父进程(PPID 5000) 死亡后,僵尸进程(PID 5021) 将成为 init 的新子进程,init 会立即清理该僵尸。

⚠️ 注意: 在结束父进程之前,请务必通过 ps -p 5000 (父 PID) 命令确认该进程是否为系统的重要服务(例如: 数据库, web 服务器等)。强制结束重要服务可能会导致更大的故障。


总结

  • 僵尸进程 是指执行结束但父进程未收获退出状态而留在进程表中的残余。

  • 虽然不消耗资源,但会占用 PID,过多时会导致系统故障。

  • 可以通过 ps -elf | grep ' Z ' 命令找到僵尸(PID)及其父进程(PPID)。

  • 解决方案针对于非僵尸的父进程(PPID)

    1. kill -s SIGCHLD <父PID> (推荐)

    2. kill <父PID> (最后手段)