在本教程中,我们将讨论一个非常有趣且内容丰富的主题,即 Linux 中的死进程或僵尸进程。 我们还将讨论 init
过程, SIGCHLD
信号,系统调用 [fork()
, exit()
, & wait()
], 和 Linux 命令 [ps
, top
, & kill
].
Linux 中的僵尸进程是什么?
在 Linux 中,一个 僵尸进程 是一个已完成执行并使用 exit() 系统调用终止的进程,但它仍然在系统的进程表中有其条目。 僵尸进程也称为 失效进程 因为它在进程表中仅用此名称表示。 实际上,Zombie 进程和原始僵尸一样,既不是活的也不是死的。
僵尸进程是如何创建的?
在 Linux 环境中,我们创建一个 子进程 使用 fork()
系统调用。 以及调用 fork()
系统调用被称为 父进程.
这个父进程必须在子进程终止后使用 **SIGCHLD 发出信号并立即呼叫 wait()
系统调用,以便它可以从系统的进程表中删除终止的子进程的条目。
这个从进程表中删除终止子进程表项的过程称为 收割. 如果父进程没有使 wait()
系统调用并继续执行其其他任务,那么它将无法读取子进程终止时的退出状态。
并且子进程的条目即使在其终止后仍将保留在进程表中。 因此它变成了一个僵尸进程。 默认情况下,每个子进程都是僵尸进程,直到其父进程等待读取其退出状态,然后从进程表中获取其条目。
**SIGCHLD 信号:当子进程停止或终止等有趣的事情发生时,会向子进程的父进程发送一个 SIGCHLD 信号,以便它可以读取子进程的退出状态。 默认情况下,对这个 SIGCHLD 信号的响应是忽略它。
用于创建僵尸进程的 C 代码。
#include <stdio.h> #include <stdlib.h> // for exit() #include <unistd.h> // for fork(), and sleep() int main() { // Creating a Child Process int pid = fork(); if (pid > 0) // True for Parent Process sleep(60); else if (pid == 0) // True for Child Process { printf("Zombie Process Created Successfully!"); exit(0); } else // True when Child Process creation fails printf("Sorry! Child Process cannot be created..."); return 0; }
输出:
该程序创建了一个僵尸进程,因为子进程使用 exit()
当父进程处于睡眠状态并且不等待其子进程读取其退出状态时的系统调用。 在 Parent 进程终止之后,Zombie 进程将只存在 60 秒,然后 Zombie 进程将自动终止。 我们可以在系统中看到这个僵尸进程为 [output] 使用以下以红色突出显示的 Linux 命令。
ubuntu:~$ ps -ef
输出:
我们还可以使用红色突出显示的 top 命令定位此 Zombie 进程的进程表条目。
ubuntu:~$ top
输出:
僵尸进程与孤儿进程
僵尸进程不应与孤儿进程混淆。 因为孤立进程是一个即使在其父进程终止后仍保持不活动或运行状态的进程,而僵尸进程保持不活动,它只保留其在系统进程表中的条目。
孤立进程可以有两种类型:
- 故意孤立的进程: 有意孤立的进程是当我们必须启动/运行无限运行的服务或完成不需要任何用户干预的长时间运行的任务时生成的孤立进程。 这些进程在后台运行,通常不需要任何手动支持。
- 无意中孤立的进程: 无意中孤立的进程是当某些父进程崩溃或终止而使其子进程处于活动或运行状态时生成的孤立进程。 与有意孤立的进程不同,这些进程可以由用户使用进程组机制来控制或避免。
僵尸进程的特征
以下是 Zombie 进程的一些特征:
- Zombie 进程的退出状态可以由父进程使用
wait()
系统调用。 - 当父进程读取僵尸进程的退出状态时,从进程表中获取其条目。
- 从进程表中收割一个僵尸进程后,其PID(进程ID)和进程表项可以被系统中的一些新进程重用。
- 如果僵尸进程的父进程被终止或完成,那么进程表中僵尸进程条目的存在会产生操作系统故障。
- 通常,可以通过使用以下命令向父进程发送 SIGCHLD 信号来销毁僵尸进程
kill
命令。 - 如果即使通过向其父进程发送 SIGCHLD 信号也无法销毁僵尸进程,那么我们可以终止其父进程以杀死僵尸进程。
- 当 Zombie 的父进程终止或完成时,Zombie 进程被
init
进程,然后通过捕获 SIGCHLD 信号并读取其退出状态来杀死 Zombie 进程,因为它不断使wait()
系统调用。
与僵尸进程相关的威胁
虽然僵尸进程不使用任何系统资源,但在系统进程表中保留其条目(PID)。 但值得关注的是系统进程表的大小有限。 每个活动进程在系统进程表中都有一个有效的条目。
如果无论如何创建了非常多的僵尸进程,那么每个僵尸进程将占用一个 PID 和系统进程表中的一个条目,并且进程表中将没有剩余空间。
这样,系统中大量Zombie进程的存在可以防止任何新进程的产生,并且系统将进入不一致状态,因为没有任何PID(进程ID)可用,进程中没有任何空间桌子。
此外,僵尸进程的存在会在其父进程不活动时产生操作系统故障。
如果系统中只有少数 Zombie 进程,这不是一个问题,但是当系统中有这么多 Zombie 进程时,这可能会成为系统的严重问题。
系统中僵尸进程的最大数量
我们可以使用以下 C 程序找到系统中僵尸进程的最大数量。 这个 C 程序在 Linux 环境中执行时,将尝试使用 fork()
已在内部调用的系统调用 while
环形。
我们知道 fork() 系统调用仅在创建子进程失败时才返回负值,因此 while
每当它成功创建子进程并且循环条件为真时 flag
柜台里面 while
循环将增加。
我们还讨论过系统中进程表的大小是有限的,因此在创建大量子进程(僵尸进程)后,进程表中将没有空间,然后 fork()
系统调用将无法再创建任何子进程,因此返回一个负值,这使得 while
循环条件为假。
通过这种方式, while
循环将停止,最后 flag
计数器值表示僵尸进程的总数。
#include<stdio.h> #include<unistd.h> // for fork() int main() { int flag = 0; // Counter variable while (fork() > 0) { flag++; // Counts the No. of Zombie Processes printf("%dn", flag); } return 0; }
输出:
从输出中,我们可以清楚地看到系统内部可以创建的最大僵尸进程数是 24679
.
笔记: 僵尸进程的最大数量是最终的值 flag
每次运行上述 C 程序时,计数器都会有所不同,具体取决于系统进程表中的空白空间。
如何杀死僵尸进程?
如果以某种方式父进程不等待其子进程的终止,它将无法捕获 SIGCHLD 信号,因此不会读取子进程的退出状态。 然后它的条目保留在进程表中,它成为一个僵尸进程。
然后我们必须通过使用以下命令向父进程发送 SIGCHLD 信号来销毁这个 Zombie 进程 kill
Linux 中的命令。
当父进程收到 SIGCHLD 信号时,它通过使用 wait()
系统调用。 以下是Linux命令手动杀死Zombie进程的演示:
ubuntu:~$ kill -s SIGCHLD <PID>
如果无论如何即使通过向父进程发送 SIGCHLD 信号也无法销毁僵尸进程,那么我们可以终止其父进程然后
如果无论如何即使通过向父进程发送 SIGCHLD 信号也不能销毁僵尸进程,那么我们可以终止其父进程,然后僵尸进程将被 在里面 进程(PID = 1)。
这个 init
进程现在成为僵尸进程的新父进程,它定期使 wait()
系统调用捕获 SIGCHLD 信号以读取 Zombie 进程的退出状态并从进程表中获取它。 以下是杀死父进程的 Linux 命令:
ubuntu:~$ kill -9 <PID>
笔记: 在上述两个 Linux 命令中,只需将
加起来
在本教程中,我们了解了 Linux 中的 Zombie Process 或 Defunct Process,它是如何在系统内部创建的,如何在系统中定位 Zombie Process,Zombie Process 和 Orphan Process 之间的区别,Zombie 的各种特性进程,与僵尸进程相关的威胁,如何找到系统中僵尸进程的最大数量,以及杀死系统中僵尸进程的不同方法。