Linux 僵尸进程产生原因及解决方法

Linux 允许进程查询内核以获得其父进程的
PID,或者其任何子进程的执行状态。例如,进程可以创建一个子进程来执行特定的任务,然后调用诸如
wait()
这样的一些库函数检查子进程是否终止。如果子进程已经终止,那么,它的终止代号将告诉父进程这个任务是否已成功地完成。

Linux
允许进程查询内核以获得其父进程的
PID,或者其任何子进程的执行状态。例如,进程可以创建一个子进程来执行特定的任务,然后调用诸如
wait()
这样的一些库函数检查子进程是否终止。如果子进程已经终止,那么,它的终止代号将告诉父进程这个任务是否已成功地完成。
为了遵循这些设计原则,不允许
Linux
内核在进程一终止后就丢弃包含在进程描述符字段中的数据。只有父进程发出了与被终止的进程相关的
wait()
类系统调用之后,才允许这样做。这就是引入僵死状态的原因:尽管从技术上来说进程已死,但必须保存它的描述符,直到父进程得到通知。
如果一个进程已经终止,但是它的父进程尚未调用
wait() 或 waitpid()
对它进行清理,这时的进程状态称为僵死状态,处于僵死状态的进程称为僵尸进程(zombie
process)。
任何进程在刚终止时都是僵尸进程,正常情况下,僵尸进程都立刻被父进程清理了。

为了遵循这些设计原则,不允许 Linux
内核在进程一终止后就丢弃包含在进程描述符字段中的数据。只有父进程发出了与被终止的进程相关的
wait()
类系统调用之后,才允许这样做。这就是引入僵死状态的原因:尽管从技术上来说进程已死,但必须保存它的描述符,直到父进程得到通知。

奥门永利误乐域,僵尸进程是如何产生的

为了观察到僵尸进程,我们自己写一个不正常的程序,父进程
fork 出子进程,子进程终止,而父进程既不终止也不调用 wait
清理子进程:

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

int main(void)
{
    int i = 100;
    pid_t pid=fork();
    if(pid < 0)
    {
        perror("fork failed.");
        exit(1);
    }
    if(pid > 0)
    {
        printf("This is the parent process. My PID is %d.\n", getpid());
        for(; i > 0; i--)
        {
            sleep(1);
        }
    }
    else if(pid == 0)
    {
        printf("This is the child process. My PID is: %d. My PPID is: %d.\n", getpid(), getppid());
    }
    return 0;
}

把上面的代码保存到文件
zomprocdemo.c 文件中,并执行下面的命令编译:

$ gcc zomprocdemo.c -o zomprocdemo

然后运行编译出来的
zomprocdemo 程序:

$ ./zomprocdemo

奥门永利误乐域 1

此时子进程已经退出,但是父进程没有退出也没有通过
wait() 调用处理子进程。我们使用 ps 命令查看进程的状态:

奥门永利误乐域 2

上图红框中的大写字母
“Z” 说明 PID 为 112712 的进程此时处于僵死的状态。

让我们接着往下看!在结束
sleep
后父进程退出。当父进程退出后,子进程会变成孤儿进程,此时它会被一个管理进程收养。在不同的系统中,这个管理进程不太一样,早期一般是
init 进程,Ubuntu 上是 upstart,还有近来的
Systemd。但是它们都完成相同的任务,就是 wiat()
这些孤儿进程,并最终释放它们占用的系统进程表中的资源。这样,这些已经僵死的孤儿进程就彻底的被清除了。

如果一个进程已经终止,但是它的父进程尚未调用 wait() 或 waitpid()
对它进行清理,这时的进程状态称为僵死状态,处于僵死状态的进程称为僵尸进程(zombie
process)。任何进程在刚终止时都是僵尸进程,正常情况下,僵尸进程都立刻被父进程清理了。

僵尸进程的危害

在进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号
PID,退出状态 the termination status of the process,运行时间 the amount
of CPU time taken by the process 等)。直到父进程通过 wait / waitpid
来取时才释放。

如果进程不调用
wait / waitpid 的话,
那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。

僵尸进程是如何产生的

如何处理僵尸进程

僵尸进程的产生是因为父进程没有
wait() 子进程。所以如果我们自己写程序的话一定要在父进程中通过 wait()
来避免僵尸进程的产生。

当系统中出现了僵尸进程时,我们是无法通过
kill
命令把它清除掉的。但是我们可以杀死它的父进程,让它变成孤儿进程,并进一步被系统中管理孤儿进程的进程收养并清理。

下面的 demo
中,父进程通过 wait() 等待子进程结束:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
        perror("fork failed");
        exit(1);
    }
    if (pid == 0) {
        int i;
         for (i = 3; i > 0; i--)
         {
            printf("This is the child\n");
            sleep(1);
         }
         // exit with code 3 for test.
        exit(3);
    }
    else
    {
        int stat_val;
        wait(&stat_val);
         if (WIFEXITED(stat_val))
         {
             printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
         }            
    }
    return 0;
}

demo
中父进程不仅等待子进程结束,还通过 WEXITSTATUS 宏取到了子进程的 exit
code。

 在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait /
waitpid)他,那么他将变成一个僵尸进程。通过ps命令查看其带有defunct的标志。僵尸进程是一个早已死亡的进程,但在进程表
(processs table)中仍占了一个位置(slot)。

但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程。因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程,看看有没有哪个
进程是刚刚结束的这个进程的子进程,如果是的话,就由Init进程来接管他,成为他的父进程,从而保证每个进程都会有一个父进程。而Init进程会自动
wait其子进程,因此被Init接管的所有进程都不会变成僵尸进程。

为了观察到僵尸进程,我们自己写一个不正常的程序,父进程 fork
出子进程,子进程终止,而父进程既不终止也不调用 wait 清理子进程:

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

int main(void)
{
  int i = 100;
  pid_t pid=fork();
  if(pid < 0)
  {
    perror("fork failed.");
    exit(1);
  }
  if(pid > 0)
  {
    printf("This is the parent process. My PID is %d.\n", getpid());
    for(; i > 0; i--)
    {
      sleep(1);
    }
  }
  else if(pid == 0)
  {
    printf("This is the child process. My PID is: %d. My PPID is: %d.\n", getpid(), getppid());
  }
  return 0;
}

把上面的代码保存到文件 zomprocdemo.c 文件中,并执行下面的命令编译:

$ gcc zomprocdemo.c -o zomprocdemo

然后运行编译出来的 zomprocdemo 程序:

$ ./zomprocdemo

奥门永利误乐域 3

此时子进程已经退出,但是父进程没有退出也没有通过 wait()
调用处理子进程。我们使用 ps 命令查看进程的状态:

发表评论

电子邮件地址不会被公开。 必填项已用*标注