【Linux】进程等待

发布于:2024-05-07 ⋅ 阅读:(25) ⋅ 点赞:(0)

思维导图

学习目标

  • 进程等待的必要性
  • 进程等待的方法
  • wait函数和waitpid函数的学习
  • 参数status的介绍

一、进程等待的必要性

1.1 进程等待是什么?

       任何子进程,在退出的情况下,一般都必须让父进程进行等到,进程在退出的时候,如果父进程不管不顾,自己退出进程,那么子进程会进入僵尸状态Z,会造成内存泄漏

什么是僵尸进程呢??

       代码已经释放,数据也进行了释放,留下了PCB。PCB中会记录这个进程瑞退出时的退出信息。这种状态一直维持,PCB结构体所占的内存时非常大的,所以必然会导致内粗泄露。

1.2 为什么要有进程等待?

  • 父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(一定要考虑的)
  • 获取子进程的退出信息,知道子进程是因为什么原因退出的(可选的功能)

1.3 进程等待的必要性:

  • 子进程退出,父进程如果不管不顾,就可能会导致僵尸进程问题,进而产生内存泄漏
  • 进程一旦变为僵尸状态,基本刀枪不入,kill - 9命令也会无动于衷,因为没有什么方法时可以将一个死去的进程给退出
  • 父进程派给子进程的任务完成的如何,我们需要知道,例如:子进程运行完成,结果是对还是不对。或者子进程有没有正常的退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

二、进程等待的方法

2.1 获取子进程的status

  • 因为在wait函数和waitpid函数中的参数都有status的参数,该参数是一个输出性参数,由操作系统来填充
  • 如果传递NULL,则不关心子进程的退出状态信息
  • 操作系统可以根据这个status的值,来将子进程的退出信息反馈给父进程
  • status不能简单地当作整数来进行学习,status当作位图来进行表示错误码和错误信息的值是多少。因为只研究低16位,具体细节如下所示:

2.1.1 详细介绍一下位图和status中各个位上的信息: 

位图是什么??

       位图(bitset)是一种常见的数据结构,常用在给一个很大范围的数(比如我之前看到一个题是有四十亿个数),判断其中的数是不是在其中。这里就要用到位图,显然这么多得数在内存中是存不下的,我们就得重新想办法。位图在索引、数据压缩有很大的应用。位图使用数组实现的,数组的每一个元素的每一个二进制位都表示一个数据,0表示该数不存在,1表示该数据存在

status各个位上的信息:

       status有32位,但是在这里只用到了16位,从0~6这7位表示的是退出信号的大小,从8~15位这8位表示的是退出码的大小。

2.1.2 通过status计算出错误码和错误信号 

int errorcode = status & 0x7f; // 错误信号的计算
int errorsignal = (status >> 8) & 0xff; // 错误码的计算

为了防止使用者将7和8弄混淆,又有专门的宏来表示退出码和推出信号。

  • WIFEXITED(status):用于查看进程是否是正常退出,本质是检查是否收到信号。
  • WEXITSTATUS(status):用于获取进程的退出码。

当一个进程的退出信号出现时,我们不需要关心退出码。

2.2 wait函数

2.2.1 wait函数的用途

       wait函数的用途是等待任何一个子进程的结束。

2.2.2 wait函数的介绍

#include <sys/wait.h>
#include <sys/types.h>

pid_t wait(int* status);

返回值:等待成功返回被等待进程的pid,等待失败返回-1。

参数:输出型参数,获取子进程的退出状态,不关心可设置为NULL。

2.2.3 使用代码进行观测

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

int main()
{
  printf("I am process, pid: %d, ppid: %d\n", getpid(), getppid());
  pid_t id = fork();
  if(id == 0)
  {
    int cnt = 5;
    while (cnt--)
    {
      sleep(1);
      printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
    }
    exit(0);
  }

  printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
  pid_t flag = wait(NULL);
  if(flag > 0)
  {
    printf("正常退出!\n");
  }
  sleep(10);
  return 0;
}

我们可以使用以下监控脚本对进程进行实时监控:

while :; do ps ajx | head -1 && ps ajx | grep a.out | grep -v grep; sleep 1; done

       当我们进行运行代码后,我们子进程退出后,父进程会立刻进行回收;之后,让父进程睡眠10秒,那么在监控脚本下,最后就只有父进程。

2.3 waitpid函数

2.3.1 waitpid函数的用途

       waitpid函数的用途是等待父进程的任意一个子进程退出或者等待一个特定的子进程退出。

2.3.2 waitpid函数的介绍

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int* status, int options);
2.3.2.1 返回值
  • 等待成功返回被等待进程的pid。

  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0。

  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。

2.3.2.2 参数
  • pid:待等待子进程的pid,若设置为-1,则等待任意子进程。
  • status:输出型参数,获取子进程的退出状态,不关心可设置为NULL。
  • options:当设置为WNOHANG时,若等待的子进程没有结束,则waitpid函数直接返回0,不予以等待。若正常结束,则返回该子进程的pid。

四、父进程的阻塞等待和非阻塞等待

       如果子进程没有退出,父进程在执行waitpid函数进行等待的时候,我们可以将父进程的状态分为阻塞等待和非阻塞等待。阻塞等待就是父进程等待子进程运行完了,父进程再运行,非阻塞等待就是父子进程同时运行。

       阻塞等待:进程阻塞了,父进程在等待某个条件的发生,父进程在等待的过程中什么事情也没有执行。waitpid函数的第三个参数填 0。

       非阻塞等待:waitpid函数的第三个参数填 WNOHANG

函数的返回值有三种情况:

  • 当 pid > 0 时,等待成功,子进程退出,并且父进程回收成功;
  • 当 pid == 0 时,检测是成功的,只不过子进程还未退出,需要进行下一次的重复等待;
  • 当 pid < 0 时,等待失败。

代码如下:

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

typedef void(*func_t)();

func_t task[3];

void Printlog()
{
  printf("this is Print\n");
}

void Download()
{
  printf("this is download\n");
}

void MySqlSync()
{
  printf("this is MySqlSync\n");
}

void loadtask()
{
  task[0] = Printlog;
  task[1] = Download;
  task[2] = MySqlSync;
}

void HanderTask()
{
  int i = 0;
  for(i = 0; i < 3; i++)
  {
    task[i]();
  }
}

void DoOtherThing()
{
  HanderTask();
}

void Chlidrun()
{
  int cnt = 10;
  while (cnt)
  {
    sleep(1);
    printf("child is doing thing, cnt: %d, pid: %d\n", cnt, getpid());
    cnt--;
  }
}

int main()
{
  printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
  pid_t id = fork();
  if(id == 0)
  {
    Chlidrun();
    printf("Child quit...\n");
    exit(123);
  }
  
  loadtask();

  while (1)
  {
    int status = 0;
    pid_t rid = waitpid(id, &status, WNOHANG);
    if(rid == 0)
    {
      sleep(1);
      printf("child is running, father can do other things\n");
      DoOtherThing();
    }
    else if(rid > 0)
    {
      if(WIFEXITED(status))
      {
        printf("Child quit success, child exit code: %d\n", WEXITSTATUS(status));
      }
      else{
        printf("Child quit failed\n");
      }
      break;
    }
    else 
    {
      printf("waitpid failed\n");
      break;
    }
  }

  return 0;
}