操作系统 什么是僵尸进程

操作系统 什么是僵尸进程

僵尸进程或者被称为‘defunct进程’是指一个已经执行完毕(通过exit系统调用)但在进程表中仍然存在的进程。这种情况通常出现在子进程中,其中进程表的条目仍然需要存在以允许父进程读取其子进程的退出状态。一旦通过wait系统调用读取了退出状态,僵尸进程的条目将从进程表中移除,称为‘reaped’。子进程在从资源表中移除之前总是首先成为一个僵尸进程。

在大多数情况下,僵尸进程会立即被其父进程等待并在正常的系统操作下由系统清除。长时间保持僵尸状态的进程通常都是错误的,并且会导致资源泄漏,但它们只占用进程表的条目。

用这个术语的比喻来说,子进程已经死亡,但尚未被清除。与普通进程不同的是,kill命令不会影响僵尸进程。

操作系统 什么是僵尸进程

僵尸进程不应与孤儿进程混淆。孤儿进程是指正在执行但其父进程已经死亡的进程。当父进程死亡时,孤儿子进程将被init(进程ID 1)接管。当孤儿进程结束时,它们不会保留为僵尸进程,而是由init等待。结果是既是僵尸进程又是孤儿进程的进程将被自动回收。

僵尸进程是如何工作的

在操作系统中,僵尸进程的工作方式如下:

  • 当进程通过退出结束时,与其相关的所有内存和资源都被释放,以便其他进程可以使用它们。
    操作系统 什么是僵尸进程

  • 然而,进程在进程表中的条目仍然存在。父进程可以通过执行等待系统调用来读取子进程的退出状态,此时僵尸进程会被移除。等待调用可以在连续代码中执行,但通常在父进程接收到子进程退出的 SIGCHLD 信号时在处理程序中执行。

  • 移除僵尸进程后,其进程标识符(PID)和进程表中的条目可以被重新使用。但是,如果父进程没有调用等待系统调用,僵尸进程将会保留在进程表中,造成资源泄漏。在某些情况下,这可能是有益的,父进程希望继续持有此资源。例如,如果父进程创建另一个子进程,它将不会被分配相同的PID。
  • 以下特殊情况适用于现代类UNIX系统。如果父进程显式地忽略了 SIGCHLD ,将其处理程序设置为 SIG_IGN (而不仅仅是默认忽略信号),或者设置了 SA_NOCLDWAIT 标志,则会丢弃所有子进程的退出状态信息,并且不会保留僵尸进程。
  • 通过UNIX的”ps”命令的输出可以识别僵尸进程,通过在”STAT”列中存在”Z”来识别。存在时间较长的僵尸进程通常表示父程序中的错误或选择不回收子进程的不常见决策。
  • 如果父程序不再运行,僵尸进程通常表示操作系统中的错误。与其他资源泄漏一样,少数僵尸进程的存在并不令人担忧,但可能表明在较重载下会变得严重的问题。由于没有为僵尸进程分配内存,系统内存的唯一使用是进程表条目本身。对于许多僵尸进程,主要关注的不是内存耗尽,而是进程表条目和具体的进程ID号的耗尽。
  • 使用KILL命令,可以手动向父进程发送 SIGCHLD 信号,以从系统中移除僵尸进程。如果父进程仍然拒绝回收僵尸进程,并且终止父进程不会有问题,下一步可以是移除父进程。当进程失去父进程时,init成为其新的父进程。Init定期执行等待系统调用以回收任何具有init作为父进程的僵尸进程。

示例

让我们看一个僵尸进程的示例。

#include 
#include 
#include 

int main(void)
{
    pid_t pids[10];
    int i;

    for (i = 9; i >= 0; --i) {
        pids[i] = fork();
        if (pids[i] == 0) {
            printf("Child%d\n", i);
            sleep(i+1);
            _exit(0);
        }
    }

    for (i = 9; i >= 0; --i) {
        printf("parent%d\n", i);
        waitpid(pids[i], NULL, 0);
    }

    return 0;
}

输出: 它给出以下输出,如下:

parent9
Child3
Child4
Child2
Child5
Child1
Child6
Child0
Child7
Child8
Child9 // there is a pause here
parent8
parent7
parent6
parent5
parent4
parent3
parent2
parent1
parent0

在第一个循环中,原始(父)进程forks了10个自身副本。每个子进程都打印一条消息,睡眠一会儿,然后退出。所有的子进程基本上是同时创建的,因为父进程在循环中几乎没有做任何事情,所以每个子进程何时被调度第一次是有些随机的,因此它们的消息的顺序是乱序的。

在循环期间,一个子进程的ID数组被构建。在所有的11个进程中都有一个 pids[] 数组的副本,但只有在父进程中才完整。在每个子进程中的副本将缺少较低编号的子进程PID,并且自己的PID为零。

第二个循环只在父进程中执行,并等待每个子进程退出。它等待睡了10秒钟的子进程,而其他的子进程早就退出了,所以所有的消息(除了第一个)都会很快地出现。这里没有随机排序的可能性,因为单个进程中的循环是驱动它的。父进程在任何子进程的进程开始之前就可以进入第二个循环。这再次只是进程调度器的随机行为 – “parent9″消息可能会在”parent8″之前的任何序列中出现。

Child0到Child8在退出和父进程进行 waitpid() 等待它们之间的状态花费了一秒或更长时间。在父进程退出之前,它已经等待Child9,因此该进程基本上没有时间作为僵尸进程。

僵尸进程的危险

僵尸进程不使用任何系统资源,但它们保留它们的进程ID。如果有很多僵尸进程,那么所有可用的进程ID都被它们垄断。这会阻止其他进程运行,因为没有可用的进程ID。

如果僵尸进程的父进程不再运行,那么这也表示操作系统存在错误。如果只有几个僵尸进程,这不是一个严重的问题,但在系统负载较重的情况下,这可能会对系统造成问题。

防止僵尸进程

我们需要防止创建僵尸进程,因为每个系统都有一个进程表,而进程表的大小是有限的。如果生成了太多的僵尸进程,那么进程表将会被填满。系统将不会生成任何新的进程,从而导致系统停滞不前。因此,我们需要防止创建僵尸进程。以下是可以防止创建僵尸的不同方法,例如:

操作系统 什么是僵尸进程

1. 调用wait()系统调用

当父进程调用wait()函数创建一个子进程后,它会等待子进程完成并获得其退出状态。父进程被挂起(在等待队列中等待),直到子进程终止。必须明确的是,在此期间,父进程什么也不做,只是等待。

// A C program to demonstrate the working of
// fork()/wait() and Zombie processes
#include
#include
#include
#include

int main()
{
    int i;
    int pid = fork();
    if (pid==0)
    {
        for (i=0; i<20; i++)
            printf("I am Child\n");
    }
    else
    {
        wait(NULL);
        printf("I am Parent\n");
        while(1);
    }
}

2. 通过忽略SIGCHLD信号

当子进程终止时,相应的SIGCHLD信号会被传递给父进程。如果我们调用signal(SIGCHLD, SIG_IGN),那么系统将忽略SIGCHLD信号,并且子进程的进程表条目将被删除。因此,不会创建僵尸进程。然而,在这种情况下,父进程无法获知子进程的退出状态。

// A C program to demonstrate ignoring
// SIGCHLD signal to prevent Zombie processes
#include
#include
#include
#include

int main()
{
    int i;
    int pid = fork();
    if (pid == 0)
        for (i=0; i<20; i++)
            printf("I am Child\n");
    else
    {
        signal(SIGCHLD,SIG_IGN);
        printf("I am Parent\n");
        while(1);
    }
}

3. 通过使用信号处理程序

父进程为SIGCHLD信号安装了一个信号处理程序。信号处理程序在其中调用wait()系统调用。在这种情况下,当子进程终止时,SIGCHLD被送达给父进程。收到SIGCHLD后,激活相应的处理程序,该处理程序调用了wait()系统调用。因此,父进程立即收集退出状态,并清除进程表中的子进程条目。因此不会创建僵尸进程。

// A C program to demonstrate handling of
// SIGCHLD signal to prevent Zombie processes.
#include
#include
#include
#include

void func(int signum)
{
    wait(NULL);
}

int main()
{
    int i;
    int pid = fork();
    if (pid == 0)
        for (i=0; i<20; i++)
            printf("I am Child\n");
    else
    {
        signal(SIGCHLD, func);
        printf("I am Parent\n");
        while(1);
    }
}

如何终止僵尸进程

可以使用kill命令向父进程发送SIGCHLD信号来终止僵尸进程。这个信号通知父进程使用wait()系统调用清理僵尸进程。这个信号可以通过kill命令发送。示例如下:

kill -s SIGCHLD pid

在上述命令中,pid是父进程的进程ID。

什么是SIGHLD信号

SIGCHLD是UNIX和类UNIX系统的信号。siginfo_t的代码值如下:

  • 子进程已正常终止 CLD_EXITED
  • 子进程异常终止(无核心文件) CLD_KILLED
  • 子进程异常终止(有核心文件) CLD_DUMPED
  • 被追踪的子进程陷入停滞 CLD_TRAPPED
  • 子进程已停止 CLD_STOPED
  • 已停止的子进程已继续 CLD_CONTINUED 当一个进程终止或停止时,它会向其父进程发送一个SIGCHLD信号。默认情况下,此信号将被忽略。如果父进程想要知道其子系统的这种状态,它应该捕获此信号。信号捕获函数通常调用wait函数来获取进程ID和其终止状态。

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程