Linux回收僵尸进程

111 阅读4分钟

在我们介绍本篇文章之前,我想介绍一下退出码,还记得C语言中main函数中都有return 0嘛,这个return 0就是退出码,我们执行程序无非就三种情况:1.执行结束,和预期一致 2.执行结束,和预期不一致 3.执行结束前,程序出错 如果子进程终止,但是父进程没有做任何操作会导致子进程一直处于这种状态。 例如我们在main函数里面写return 10,使用**echo ?可以查看到退出码,不过我们只能使用一次,读者可以试试使用ls之后在使用echo?**可以查看到退出码,不过我们只能使用一次,读者可以试试使用ls之后在使用echo ?

image.png 那么这个退出码怎么获取呢?我们可以写如下的程序获取退出码的含义

#include<stdio.h>
#include<string.h>
int main()
{
  int i=0;
  for(i=0;i<200;++i)
  {
    printf("%d %s\n",i,strerror(i));
  }
}

image.png _exit和exit的区别 _exit是系统调用不会刷新缓冲区,exit是库函数

image.png 僵尸进程的危害

1.进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的! 2.维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的 3.那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间

我们的解决方法是:父进程通过进程等待的方式,回收子进程资源

一、进程等待的方法

1.1 wait方法

我们可以在linux下输入man 2 wait来查看wait的用法 :::info #include<sys/types.h> #include<sys/wait.h> pid_t wait(int*status); 返回值: 成功返回被等待进程pid,失败返回-1。 参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL ::: 代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
  pid_t id =fork();
  if(id==0)
  {
    int cnt=5;
    //子进程
    while(--cnt)
    {
      printf("我是子进程,我的pid是%d,我的ppid是%d\n",getpid(),getppid());
      sleep(1);

    }
 //   exit(0); //进程退出
  }
  sleep(10);
  pid_t ret=wait(NULL);
  if(id>0)
  {
    printf("wait success\n");
  }
  sleep(5);
  return 0;
}

通过这样的操作我们发现子进程从僵尸状态下被回收了 image.png

1.2 waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options); 返回值: 当正常返回的时候waitpid返回收集到的子进程的进程ID; 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0; 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在; 参数: pid: pid=-1,等待任一个子进程。与wait等效。 Pid>0.等待其进程ID与pid相等的子进程。 status: WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码) options: WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进 程的ID。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
  pid_t id =fork();
  if(id==0)
  {
    int cnt=5;
    //子进程
    while(--cnt)
    {
      printf("我是子进程,我的pid是%d,我的ppid是%d\n",getpid(),getppid());
      sleep(1);

    }
 //   exit(10); //进程退出
  }
  int status=0;
  //这里的0表示阻塞式等待
  pid_t ret=waitpid(id,&status,0);
  if(ret>0)
  {
    printf("wait success:%d,ret:%d\n",ret,status);
  }
  return 0;
}

在我们输入这段代码以后理论上来说输出子进程的pid和退出码10 image.png 但是这个status和我们预想的不一样,原因在于 image.png 我们可以这样解决:

 printf("wait success:%d,sig number:%d,child exit code:%d\n",ret,status&0x7F,(status>>8)&0xFF);

运行以后有如下结果: image.png 关于信号的意义我们可以使用kill -l去查看其意义 我们可以通过这样的图去理解其内部意义 image.png 有心的读者可能会想在这期间父进程在干嘛,答案是父进程一直等待子进程结束,他什么事情都不干,也称之为阻塞式等待,如果我们想让父进程再次期间做事情,我们就需要非阻塞式等待

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
  pid_t id =fork();
  if(id==0)
  {
    int cnt=5;
    //子进程
    while(--cnt)
    {
      printf("我是子进程,我的pid是%d,我的ppid是%d\n",getpid(),getppid());
      sleep(1);

    }
    exit(0); //进程退出
  }
  int status=0;
  //这里的WNOHANG表示非阻塞式等待
  while(1)
  {
    pid_t ret=waitpid(id,&status,WNOHANG);
    if(ret==0)
    {
      sleep(1);
      //调用成功&&子进程没有退出
      //可以让子进程做别的事情
      printf("我正在做别的事情\n");
    }
    if(ret>0)
    {
      //调用成功&&子进程退出了
      printf("wait success:%d,sig number:%d,child exit code:%d\n",ret,status&0x7F,(status>>8)&0xFF);
      //printf("wait success:%d,child exit code:%d\n",ret,WIFEXITED(status));
      break;
    }
    else
    {
      //调用失败

      printf("wait failed");
      break;
    }
  }
    return 0;
}

image.png 这样我们就可以实现双执行流