关于僵尸进程

  • 僵尸进程的相关知识点
  • 僵尸进程的产生原因
  • 僵尸状态的作用
  • 僵尸进程的解决
  • wait函数
  • 代码示例
  • waitpid及其使用
  • 通过信号消灭僵尸进程
  • 写在后面


僵尸进程的相关知识点

僵尸进程的产生原因

僵尸进程是什么?

进程完成工作后应被销毁,但有时候这些进程将变成僵尸进程,占用系统中的重要资源。这种状态下的进程称作“僵尸进程”

我们都知道,进程的常用三种状态是

  • 就绪
  • 运行
  • 等待

而实际上,进程的状态信息如下

enum proc_state { UNUSED, EMBRYO, SLEEPING,
RUNNABLE, RUNNING, ZOMBIE }; 
// 僵尸状态:一个进程可以处于已退出但尚未清理的最终(final)状态

而运行结束或者被中止生命周期的进程就会进入僵尸状态zombie。在linux通过ps -ef可以查看到,处于僵尸进程的会显示为

ProcessBuilder 僵尸进程 僵尸进程状态_linux


僵尸进程产生的原因:

了解清楚了僵尸进程是什么(一种占用资源的、不可运行的、应该销毁的进程)后。我们需要分析,它怎么来的。为什么进程结束了不直接销毁。

向exit函数传递的参数值和main函数的return语句返回的值都会传递给操作系统。而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。将子进程变成僵尸进程的是操作系统

僵尸状态的作用

有同学可能要和我最开始一样,子进程都运行结束了。还有什么用啊。还占着内存空间等资源。还需要我们父进程去处理。(白发人送黑发人)
但是,在读过相关书籍《操作系统导论》后。我就发现,僵尸进程还真有点用

这个最终状态非常有用,因为它允许其他进程(通常是创建进程的父进程)检查进程的返回代码,并查看刚刚完成的进程是否成功执行

看到没有,可以告诉父进程,子进程是否成功运行。因为我们可以在子进程的代码逻辑里面,各种exit,return,父进程就可以通过这个来检测代码运行情况。

至于父进程怎么判断子进程的运行状况,且耐着性子往下看

僵尸进程的解决

父进程需要给子进程“收尸”,可是他们之间似乎从fork之后的就各做各的去了。这时候,就需要提供一些额外手段了。

  1. wait函数
  2. waitpid函数
  3. 信号SIGCHLD

前两种很好理解,意思是父进程早就知道会出现“白发人送黑发人的情况”。那么就早早地通过这两个函数等待子进程的运行结束。而第三种,表示以信号处理的方式来销毁僵尸进程

wait函数

首先看下wait函数在linux下的定义

#include <sys/wait.h>

pid_t wait(int *status);
// 以阻塞的方式等待一个子进程的终止。
// 返回值:
// 1. 子进程ID
// 2. -1,函数出错

调用此函数时如果已有子进程终止,那么子进程终止时传递的返回值(exit函数的参数值,main函数的return返回值)将保存到该函数的参数所指内存空间。但函数参数指向的单元中还包含其他信息。因此需要通过下列宏进行分离:

  1. WIFEXITED子进程正常终止时返回真
  2. WEXITSTATUS返回子进程的返回值

如果没有已终止的子进程,那么程序将阻塞直到有子进程终止。因此考虑使用waitpid函数

代码示例

因此,在向wait函数传递变量status的地址时,调用wait函数后应该通过下列形式调用

#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <signal.h>

void testWait()
{
   int status = 0;
   wait(&status);
   if (WIFEXITED(status))
   {
        puts("normal termination");
        printf("Child pass num: %d\n", WEXITSTATUS(status));
   }
   else
   {
        puts("non-normal termination");
        printf("Child pass num: %d\n", WEXITSTATUS(status));
        
   }
}

int main(void)
{
    pid_t pid;
    printf("before fork\n");
    pid = fork();
    if (pid == 0)
    {
        printf("entering child process, child pid = %d\n", getpid());
    }
    else
    {
        printf("continue parent process, %d\n", getpid());
    //    wait(&status);
    //    if (WIFEXITED(status))
    //    {
    //        puts("normal termination");
    //        printf("Child pass num: %d\n", WEXITSTATUS(status));

    //    }
        testWait();
    }
    return 0;
}

ProcessBuilder 僵尸进程 僵尸进程状态_僵尸进程_02

waitpid及其使用

通过信号消灭僵尸进程

写在后面

已经讲清楚了,僵尸进程是什么。以及为什么要有僵尸进程,而不是直接结束以后就销毁。还有父进程怎么去销毁或者获取子进程的运行状态。
但是因为时间关系,就先写个上篇。做个记录。等空了写个下篇。
凡事要认真。