Linux系统中僵尸进程是怎么产生的?如何清理?

Linux系统中僵尸进程是怎么产生的?如何清理?

你运行ps aux,看到一行奇怪的输出:[python] <defunct>。进程名后面跟着<defunct>,状态列是Z。这是一个僵尸进程。它不占CPU,不占内存,但占着进程表里的一个位置。如果僵尸进程太多,系统可能无法创建新进程。

今天把僵尸进程的产生原因和清理方法讲清楚。

先看一个数据

僵尸进程本身不消耗CPU和内存,但会占用进程ID(PID)资源。每个系统能创建的进程数量是有限的(默认PID最大值通常为32768)。如果僵尸进程数量达到上万级别,系统可能无法启动新的服务或应用,从而导致可用性中断。

僵尸进程是怎么产生的

在Linux系统中,每个进程退出时都会经历一个状态转换。父进程通过wait()waitpid()系统调用来获取子进程的退出状态。如果父进程没有调用这些函数来“收尸”,子进程就会进入僵尸状态。

一个简单的产生僵尸进程的C程序示例

c

#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程直接退出
        printf("Child process exiting\n");
    } else {
        // 父进程故意不调用wait()
        printf("Parent process sleeping\n");
        sleep(60);
    }
    return 0;
}

这个程序创建了一个子进程后,父进程不调用wait()回收子进程,子进程就会变成僵尸进程。

僵尸进程的完整生命周期

进程退出时的典型流程

  1. 进程通过exit()系统调用退出
  2. 内核释放进程的大部分资源(内存、文件描述符等)
  3. 进程状态变为Z(僵尸),进程描述符保留在进程表中,等待父进程调用wait()获取退出状态
  4. 父进程调用wait(),内核释放最后的进程描述符,僵尸进程消失
  5. 如果父进程没有调用wait(),僵尸进程会一直保留在进程表中

内核必须保留僵尸进程的进程描述符,因为父进程可能需要读取子进程的退出码(0表示成功,非0表示错误)。父进程读取后,僵尸进程才会被彻底清理。

如何识别僵尸进程

用ps命令查看

bash

ps aux | grep Z
ps -ef | grep defunct

状态为Z的进程就是僵尸进程ps aux输出中STAT列显示ZCOMMAND列显示<defunct>

查看僵尸进程数量

bash

ps aux | grep -c Z

如何清理僵尸进程

僵尸进程已经死了,你无法用kill杀死它。kill -9对僵尸进程无效,它已经在等父进程回收了。

清理僵尸进程的唯一方法:杀死它的父进程

找到父进程

bash

ps -o ppid= -p 僵尸PID

或使用pstree查看进程树:

bash

pstree -p 父PID

杀死父进程

bash

kill -15 父PID
# 如果不行,用 -9
kill -9 父PID

父进程被杀死后,僵尸进程会变成孤儿进程,被init进程(PID 1)收养。init进程会定期调用wait()回收孤儿进程,僵尸进程就会被清理掉。

如果父进程是PID 1(init/systemd)

理论上PID 1不会被杀死,这种情况比较棘手。通常意味着系统级服务有bug。最常见的解决方案是重启整个服务器。但这应该是最后的手段。

确认僵尸进程是否已清理

bash

ps aux | grep 僵尸PID
# 如果查不到,说明已经被回收了

僵尸进程堆积的常见原因

1. 父进程代码有bug

父进程创建子进程后,没有调用wait()waitpid()来回收子进程。这是最常见的原因。

2. 父进程被挂起或阻塞

父进程卡在某个操作中(如死循环、等待I/O),无法执行到wait()调用。父进程还活着,但永远无法“收尸”。

3. 父进程先于子进程退出

如果父进程在子进程之前退出,子进程变成孤儿进程,由init进程收养。init进程会回收,通常不会产生僵尸。僵尸进程产生的条件只能是父进程仍在运行但未回收子进程。

如何预防僵尸进程

1. 信号处理

父进程注册SIGCHLD信号处理函数,在信号处理函数中调用wait()

c

signal(SIGCHLD, sigchld_handler);

2. 使用waitpid(-1, NULL, WNOHANG)

不阻塞父进程,周期性地检查并回收子进程。

3. 双重fork技巧

让子进程再创建一个孙进程,然后子进程立即退出,孙进程由init进程收养。这样init进程会负责回收,父进程不用担心子进程变成僵尸。

真实案例

某台服务器运行了一个Python脚本,每隔几分钟创建一个子进程去处理任务。脚本没有正确回收子进程,运行三天后产生了800多个僵尸进程。虽然不消耗CPU,但进程表快满了,新的定时任务无法启动。kill无法杀死僵尸,最终通过pstree找到父进程并重启了Python脚本,所有僵尸进程被init进程回收。

最后一句

僵尸进程清理的本质是让父进程执行wait()。如果父进程不正常,就杀死父进程,让init进程接管。kill -9杀不掉僵尸,这是很多人容易走错的弯路。如果你的服务器上僵尸进程数量持续增长,说明父进程代码有bug,需要从源头修复。杀死父进程只是治标。找到那段不调用wait()的代码,才是治本。

知识库

服务器SSH登录慢的终极排查指南

2026-6-30 14:18:13

知识库

“端口”到底是个啥?为何我的网站/应用需要“监听”特定端口才能工作?

2025-5-26 16:06:26

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧