【Linux】进程等待

简介: 【Linux】进程等待

思维导图

学习目标

  • 进程等待的必要性
  • 进程等待的方法
  • 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;
}

   


相关文章
|
8月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
310 67
|
7月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
231 16
|
7月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
148 20
|
6月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
138 0
|
6月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
211 0
|
6月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
137 0
|
6月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
188 0
|
9月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
366 4
|
9月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
9月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
754 5