什么是僵尸进程?僵尸进程是如何产生如何消失的,其危害和处理

什么是僵尸进程?僵尸进程是如何产生如何消失的,其危害和处理

讲到进程,我们要先了解一下另一个概念:程序。程序说白了就是躺在电脑硬盘上的一个文件而已(如同硬盘女神一样),在被 CPU 执行之前,它啥也做不了。

当程序被执行之后,它运行的实例就称为进程 。一个程序可以对应多个进程。进程是系统的工作单元。系统由多个进程组成,其中有的是操作系统进程(执行系统代码),其他的是用户进程(执行用户代码)。所有这些进程都会并发执行,例如通过在单 CPU 上采用多路复用来实现。

你可以使用 ps 命令查看 Linux 系统中的所有进程 。

当一个进程调用 fork 函数生成另一个进程,原进程就称为父进程,新生成的进程则称为子进程。

Linux 系统中这样父子进程非常多,我们可以使用 pstree 命令查看系统上的进程「谱系」,到底谁是谁的父亲。

pstree

每个进程在系统中都被分配了一个编号。在这所有的进程中,有个非常特殊的进程,它的 ID 号是 1 。它是系统在引导过程中执行的第一个进程,PID 1 之后的每个后续进程都是它的后代。

一) 什么是僵尸进程?

僵尸进程(Zombie Process)是指子进程已经终止,但其父进程尚未回收它的退出状态信息的进程。在 Linux/Unix 系统中,进程终止后,内核仍然会保留该进程的一些信息(如进程 ID、退出状态等),以便父进程可以通过 wait() 或 waitpid() 获取子进程的终止状态。

在 Linux 环境中,我们是通过 fork 函数来创建子进程的。创建完毕之后,父子进程独立运行,父进程无法预知子进程什么时候结束。

通常情况下,子进程退出后,父进程会使用 wait 或 waitpid 函数进行回收子进程的资源,并获得子进程的终止状态。

但是,如果父进程先于子进程结束,则子进程成为孤儿进程。孤儿进程将被 init 进程(进程号为1)领养,并由 init 进程对孤儿进程完成状态收集工作。

而如果子进程先于父进程退出,同时父进程太忙了,无瑕回收子进程的资源,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程,如下图所示:

僵尸进程 – Zombie Process

二) 僵尸进程的产生过程

前面已经介绍了僵尸进程产生的原理,下面我们通过代码来模拟僵尸进程的产生。

123456789101112131415161718192021222324

#include #include #include #include int main(void) { pid_t pid; pid = fork(); if (pid == 0) { printf("I am child, my parent= %d, going to sleep 3s\n", getppid()); sleep(3); printf("-------------child die--------------\n"); } else if (pid > 0) { printf("I am parent, pid = %d, myson = %d, going to sleep 5s\n", getpid(), pid); sleep(5); system("ps -o pid,ppid,state,tty,command"); } else { perror("fork"); return 1; } return 0; }

在这个程序里,父进程创建子进程之后,就休眠 5 秒钟。而子进程只休眠 3 秒钟就退出,在它退出之后,父进程还未苏醒,因此没人给子进程「收尸」,所以它就变成了僵尸进程。

父进程创建子进程(调用 fork())。

子进程执行任务并终止(调用 exit() 或因某种原因崩溃)。

子进程的终止状态等待被回收。

如果父进程调用 wait() 或 waitpid(),子进程的资源被释放,避免成为僵尸进程。

如果父进程没有回收子进程的退出状态,该子进程就会进入僵尸状态。

三) 僵尸进程的特征

僵尸进程(Zombie Process)是指子进程已经终止,但父进程尚未回收其退出状态的进程。它有以下特征:

3.1) 进程状态为 Z(Zombie)

在 Linux/Unix 系统中,ps 或 top 命令可以查看进程的状态(STAT)。

僵尸进程的状态标记为 Z,表示它处于“Zombie”状态。

STAT 列中的 Z 表示该进程是僵尸进程。

COMMAND 列显示 ,表示该进程已经终止但未被回收。

3.2) 不占用 CPU 和内存

僵尸进程不会消耗 CPU 和内存,因为它已经终止。

但是,它仍然保留在 进程表(task_struct) 中,占用一个 进程 ID(PID),直到父进程回收它的退出状态。

3.3) 无法被 kill -9 直接杀死

僵尸进程已经终止,不能被 SIGKILL(kill -9)杀死。

但可以终止其父进程,让 init 进程(PID 1)接管并回收它:

1

kill -9 <parent_pid>

或者重启父进程:

1

systemctl restart <service_name>

3.4) 父进程未调用 wait() 或 waitpid()

僵尸进程的根本原因是父进程没有回收子进程的退出状态。

如果父进程崩溃或被错误编写,可能导致大量僵尸进程积累。

3.5) 进程 ID 仍然被占用

僵尸进程的 PID 不会被重新分配,直到其父进程回收它。

如果系统中出现过多僵尸进程,可能会导致 PID 资源耗尽,无法创建新进程。

3.6) 可能影响父进程的行为

父进程可能进入异常状态,如:

无法正确处理子进程退出事件。

由于未回收子进程,导致内核进程表(process table)持续增长。

如果父进程死循环产生僵尸进程,可能拖垮整个系统。

3.7) 总结

特征

描述

进程状态 Z(Zombie)

ps aux 显示 STAT 为 Z,top 显示

不占用 CPU 和内存

但仍占用进程表中的条目(PID)

无法被 kill -9 直接杀死

需要让父进程 wait() 处理,或终止父进程

父进程未回收子进程

没有调用 wait() 或 waitpid()

占用进程 ID 资源

可能导致新进程无法创建

影响父进程运行

可能导致异常行为,甚至系统崩溃

四) 僵尸进程的危害

僵尸进程(Zombie Process)本身不占用 CPU 或内存资源,但如果大量存在,会对系统稳定性和可用性产生严重影响。以下是它可能带来的危害:

4.1) 占用进程 ID(PID)资源

系统中的进程 ID 是有限的(通常是 32768 或 4194304,取决于系统配置)。

如果僵尸进程过多,导致 PID 被占满,新进程将无法创建,影响系统运行。

4.2) 影响系统监控工具

ps, top, htop 等命令会显示僵尸进程(状态 Z),如果数量过多,会影响这些工具的正常使用。

可能导致运维人员误判系统运行状况,增加运维负担。

4.3)可能影响父进程的运行

父进程需要回收子进程的退出状态(调用 wait() 或 waitpid())。

如果父进程未正确处理,可能持续积累僵尸进程,导致父进程功能异常甚至崩溃。

4.4) 系统资源浪费

尽管僵尸进程不占用 CPU 和内存,但进程表(task_struct)仍然会存储僵尸进程的信息。

这些数据会消耗内核资源,影响系统整体性能。

4.5) 安全风险

攻击者可能利用僵尸进程进行 DOS(拒绝服务)攻击:

创建大量子进程,然后使它们变成僵尸,导致系统资源耗尽,影响系统稳定性。

可能引发软件 bug 或内存泄漏:

一些未考虑僵尸进程的程序,可能因处理异常而崩溃。

4.6) 可能导致系统崩溃

在极端情况下,大量僵尸进程会导致进程表溢出,影响系统的进程管理功能,使系统无法创建新进程,甚至导致内核崩溃(kernel panic)。僵尸进程可能成为恶意攻击的目标(如 PID 污染攻击)

例如:

在服务器上运行大量短生命周期的子进程,如果父进程未正确回收它们,可能引发系统崩溃。

五) 如何处理僵尸进程?

对于普通进程,我们可以通过使用 kill 命令来杀死它们。kill 命令它还有几个兄弟,比如 pkill 和 killall ,虽然它们名称里都带 kill 这样杀气腾腾的字眼,但它们实际上是被设计为向一个或多个进程发送信号。

在未指定的情况下,这几个命令默认发送的是 SIGTERM 信号。

普通进程可以被 kill ,但僵尸进程是不行的。为什么?因为僵尸进程本身就已经「死」过一次了!如果还可以再「死」,那「僵尸」这个名号就没多大意义了。

僵尸进程其实已经就是退出的进程,因此无法再利用kill命令杀死僵尸进程。僵尸进程的罪魁祸首是父进程没有回收它的资源,那我们可以想办法它其它进程去回收僵尸进程的资源,这个进程就是 init 进程。

因此,我们可以直接杀死父进程,init 进程就会很善良地把那些僵尸进程领养过来,并合理的回收它们的资源,那些僵尸进程就得到了妥善的处理了。

例如,如果 PID 5878 是一个僵尸进程,它的父进程是 PID 4809,那么要杀死僵尸进程 (5878),您可以结束父进程 (4809):

1

$ sudo kill -9 4809 #4809 is the parent, not the zombie

杀死父进程时要非常小心,如果一个进程的父进程就是 PID 1 ,并且你还杀死了它,那么系统将直接重启!

方法 1:让父进程主动回收

在父进程中调用 wait() 或 waitpid() 来处理子进程的退出状态。例如:

123456

int pid = fork();if (pid > 0) { wait(NULL); // 回收子进程} else if (pid == 0) { exit(0); // 子进程退出}

方法 2:让父进程忽略 SIGCHLD 信号

通过 signal(SIGCHLD, SIG_IGN);,让内核自动清理子进程:

1

signal(SIGCHLD, SIG_IGN);

方法 3:结束或重启父进程

如果父进程没有正确处理僵尸进程,可以尝试:

结束父进程:kill -9 ,init 进程(PID 1)会接管并清理僵尸进程。

重启父进程:有时可以解决僵尸进程堆积的问题。

方法 4:使用 prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);

适用于 Linux,可以让祖先进程接管孤儿进程的回收工作。

六) 相关 Linux 命令

查看僵尸进程

1

ps aux | grep Z

查看某个进程是否有僵尸子进程

1

ps -eo ppid,pid,stat,cmd | grep ' Z '

杀死父进程(慎用)

1

kill -9 <parent_pid>

七) 僵尸进程 vs. 孤儿进程

类型

产生原因

影响

解决方法

僵尸进程

子进程退出但父进程未回收

占用 PID,不会释放

让父进程 wait() 或终止父进程

孤儿进程

父进程退出但子进程仍在运行

由 init 进程接管,不会成为僵尸

无需特别处理

八) 预防僵尸进程

使用 wait() 或 waitpid() 处理子进程的退出。

让父进程忽略 SIGCHLD 信号。

采用守护进程(daemon)机制,如 double fork 避免僵尸进程:

123

if (fork() > 0) exit(0); // 让父进程退出,子进程变为孤儿进程setsid(); // 使其成为新的会话 leaderif (fork() > 0) exit(0); // 让第一个子进程退出,第二个子进程变成守护进程

九) 总结

僵尸进程是已经终止但未被回收的进程,占用 PID 资源。

父进程应及时调用 wait() 或 waitpid() 处理子进程。

可以通过 SIGCHLD 信号处理、重启父进程等方法清理僵尸进程。

大量僵尸进程会影响系统稳定性,应预防其产生。

wer

Entires个相关

Zombie

Daemon

守护进程

管道

除教程外,本网站大部分文章来自互联网,如果有内容冒犯到你,请联系我们删除!

相关典藏

员工绩效考核个人总结及自评范文(精选6篇)
365bet.com亚洲版

员工绩效考核个人总结及自评范文(精选6篇)

📅 08-09 👁️‍🗨️ 596
为什么你需要学习使用Markdown语法
365bet.com亚洲版

为什么你需要学习使用Markdown语法

📅 08-25 👁️‍🗨️ 2459
结扎,该“扎”男人还是“扎”女人?
我和黑大佬的365天知乎

结扎,该“扎”男人还是“扎”女人?

📅 10-05 👁️‍🗨️ 147