如何在 Linux 上杀死僵尸进程

编写不当或性能不佳的程序会使僵尸进程潜伏在您的 Linux 计算机中。了解僵尸是如何产生的,以及如何最终让它们安息。
进程状态如何在 Linux 上工作
当然,Linux 必须跟踪计算机上运行的所有应用程序和守护进程。它执行此操作的方法之一是维护进程表。这是内核内存中的结构列表。每个进程在此列表中都有一个条目,其中包含一些关于它的信息。
每个进程表结构中都没有太多内容。它们包含进程 ID、一些其他数据项以及指向该进程的进程控制块 (PCB) 的指针。
PCB 包含 Linux 需要为每个进程查找或设置的许多细节。 PCB 也会随着进程的创建、给定的处理时间和最终销毁而更新。
Linux PCB 包含超过 95 个字段。它被定义为一个名为 task_struct.h
的结构,长度超过 700 行。 PCB 包含以下类型的信息:
- 过程状态:状态描述如下。
- 进程号:它在操作系统中的唯一标识符。
- 程序计数器:当此进程下一次获得 CPU 访问权限时,系统将使用此地址查找应该执行的进程的下一条指令。
- Registers:此进程使用的 CPU 寄存器列表。该列表可能包含累加器、索引寄存器和堆栈指针。
- 打开文件列表:与此进程关联的文件。
- CPU 调度信息:用于确定将 CPU 处理时间授予该进程的频率和时长。进程的优先级、指向调度队列的指针和其他调度参数必须记录在 PCB 中。
- 内存管理信息:有关此进程正在使用的内存的详细信息,例如进程内存的起始地址和结束地址,以及指向内存页面的指针。
- I/O 状态信息:进程使用的任何输入或输出设备。
“进程状态”可以是以下任何一种:
- R:正在运行或可运行的进程。运行意味着它正在接收 CPU 周期并执行。可运行进程已准备好运行并等待 CPU 插槽。
- S:一个睡眠过程。该流程正在等待操作完成,例如输入或输出操作,或等待资源可用。
- D: 进程处于不可中断的睡眠状态。它正在使用阻塞系统调用,并且在系统调用完成之前无法继续。与“睡眠”状态不同,处于此状态的进程在系统调用完成并且执行返回到进程之前不会响应信号。
- T: 进程已终止(停止),因为它收到了
SIGSTOP
信号。它只会响应SIGKILL
或SIGCONT
信号,这两种信号分别终止进程或指示进程继续。这是当您从前台 (fg
) 任务切换到后台 (bg)
任务时发生的情况。 - Z:僵尸进程。当一个进程完成时,它不会消失。它释放它正在使用的所有内存并将其自身从内存中删除,但它在进程表和 PCB 中的条目仍然存在。它的状态设置为
EXIT_ZOMBIE
,并且(通过SIGCHLD
信号)通知其父进程子进程已完成。
在 Zombie 状态下,父进程在创建子进程时调用 wait()
函数族之一。然后它等待子进程中的状态更改。子进程是否被信号停止、继续或终止?它是否通过运行其代码的自然完成而终止?
如果状态变化意味着子进程已停止运行,则读取其退出代码。然后,孩子的 PCB 被销毁,它在进程表中的条目被删除。理想情况下,这一切都发生在眨眼之间,处于僵尸状态的进程不会存在很长时间。
是什么导致了 Linux 上的僵尸进程?
编写不当的父进程可能不会在创建子进程时调用 wait()
函数。这意味着没有任何东西在监视子进程中的状态变化,SIGCHLD
信号将被忽略。或者,也许另一个应用程序正在影响父进程的执行,这可能是由于编程不当或出于恶意。
但是,如果父进程没有监视子进程中的状态变化,则不会发生适当的系统内务处理。当子进程终止时,PCB 和进程表中的条目不会被删除。这导致僵尸状态永远不会从 PCB 上移除。
Zombies 确实会占用一些内存,但它们通常不会造成问题。进程表中的条目很小,但是,在它被释放之前,进程 ID 不能被重用。在 64 位操作系统上,这不太可能导致任何问题,因为 PCB 比进程表条目大得多。
可以想象,大量的僵尸可能会影响其他进程可用的内存量。但是,如果您有那么多僵尸,则说明您的父应用程序或操作系统存在严重问题。
如何删除僵尸进程
你不能杀死一个僵尸进程,因为它已经死了。它不会响应任何信号,因为它已从内存中删除——没有地方可以发送 SIGKILL
信号。您可以尝试向父进程发送 SIGCHLD
信号,但如果它在子进程终止时不起作用,那么现在也不太可能起作用。
唯一可靠的解决方案是杀死父进程。当它终止时,它的子进程由 init
进程继承,这是 Linux 系统中第一个运行的进程(它的进程 ID 为 1)。
init
进程定期执行必要的僵尸清理,因此要杀死它们,您只需杀死创建它们的进程即可。 top
命令是查看您是否有僵尸的便捷方式。
输入以下内容:
top

这个系统有八个僵尸进程。我们可以使用 ps
命令并将其通过管道传输到 egrep
来列出这些。同样,僵尸进程的状态标志为“Z”,您通常还会看到“defunct”。
输入以下内容:
ps aux | egrep "Z|defunct"

列出僵尸进程。

与在 top
中来回滚动相比,这是一种发现僵尸进程 ID 的更简洁的方法。我们还看到一个名为“badprg”的应用程序产生了这些僵尸。
第一个僵尸的进程ID是7641,但是我们需要找到它的父进程的进程ID。我们可以再次使用 ps
来做到这一点。我们将使用输出选项 (-o
) 告诉 ps
仅显示父进程 ID,然后将其与 ppid=
一起传递> 旗帜。
我们要查找的进程将通过使用 -p
(进程)选项来指示,然后传入僵尸的进程 ID。
因此,我们键入以下命令来查找进程7641的进程信息,但它只会报告父进程的ID:
ps -o ppid= -p 7641

我们被告知父进程 ID 是 7636。我们现在可以再次使用 ps
来交叉引用它。

我们看到这与之前的父进程名称相匹配。要终止父进程,请使用带有 kill 命令的 SIGKILL 选项,如下所示:
kill -SIGKILL 7636
根据父进程的所有者,您可能还需要使用 sudo
。
僵尸并不可怕……
……除非他们成群结队。有一些不用担心,简单的重启即可将它们清除。
但是,如果您注意到某个应用程序或进程总是在生成僵尸,那么您应该调查一下。它很可能只是一个草率编写的程序,在这种情况下,也许有一个更新版本可以在其子进程之后正确清理。