那年我手执『wait』桃木剑,轻松解决僵尸进程~

news2024/11/25 8:07:15

在这里插入图片描述

文章目录

  • 💐专栏导读
  • 💐文章导读
  • 🐧进程退出
    • 🐦进程常见的退出方法
      • 🐔正常终止
        • 🔔return 退出
        • 🔔exit 退出
        • 🔔_exit 退出
      • 🐔异常终止
  • 🐧进程等待
      • 🐦必要性
      • 🐦是什么
      • 🐦如何等待
        • 🔔解决子进程僵尸问题
        • 🔔如何获取子进程status
    • 🦋阻塞等待
    • 🦋非阻塞等待
      • 🐦完整代码

💐专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 Linux从入门到精通,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法

💐文章导读

前几章我们讲了关于如何创建进程与进程状态。那么本章我们就来看看进程在退出时又有哪些花样吧~ 为了解决之前所讲的僵尸进程问题,我们必须要让父进程得到子进程的退出状态,这就是本章的另一个话题——进程等待~

在这里插入图片描述

🐧进程退出

思考一下,我们创建一个进程的目的是什么?当然是想让进程帮助我们完成某件事情。例如:①将一个文件拷贝到某个目录下、②判断某个某个文件是否为空…

有些情况下,我们只需要进程去做某件事即可,并不需要关心它做的是否合格(这显然是不可取的行为);有时候不仅需要进程去做某件事,还需要关心它是否成功了,即关心一个进程的结果。

那么当一个进程结束时,一共可分为3中情况:

  1. 代码运行完毕,且结果正确;
  2. 代码运完毕,结果错误;
  3. 代码异常终止;

🐦进程常见的退出方法

🐔正常终止

🔔return 退出

在C语言中,我们编写程序时都会在main函数的最后加上一条语句——return 0; main函数中的return并不等同于其他函数的returnmain函数返回的其实是进程退出码

在Linux中,我们可以使用指令来查看一个进程的退出码:

$ echo $?
  • 只会保存最近一次进程退出的退出码。

示例

#include <stdio.h>
int main()
{
    int a = 10;
    int b = 30;

    int ret = a + b; // 代码1
    //int ret = a + b / 2; // 代码2
    // 结果正确返回0,错误返回1 
    if(ret == 40)
        return 0;
    else 
      return 1;
    printf("mytest......\n");
}

代码1
在这里插入图片描述
代码2

在这里插入图片描述

🔔exit 退出

我们可能对这个函数并不陌生,它的作用就是终止进程。exit的参数就是退出进程时需要返回的退出码。

我们故意写一段错误的代码来看看exit返回的退出码:

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

void Sort(int* array,int n)
{
  if(array == NULL)
  {
     perror("Array Fail");
     exit(111);
  }
  // 排序略...
}
int main()
{
    int * prt = NULL;
    Sort(prt,10);
	return 0}

在这里插入图片描述

🔔_exit 退出

_exit系统调用,并不像exit是C语言的库函数_exitexit使用方法完全相同,但是两者某些行为却有差别:

  • exit在退出进程之前会刷新缓冲区
  • _exit直接退出进程;

示例

exit 的表现

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

void Sort(int* array,int n)
{
  if(array == NULL)
  {
     //per1ror("Array Fail");
     printf("函数发生错误");
     sleep(1);
     exit(111);
  }
  // 排序略...
}
int main()
{   
    int * prt = NULL;
    Sort(prt,10);
    return 0;
}

在这里插入图片描述

_exit 的表现

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

void Sort(int* array,int n)
{
  if(array == NULL)
  {
     //per1ror("Array Fail");
     printf("函数发生错误");
     sleep(1);
     _exit(111);
  }
  // 排序略...
}
int main()
{   
    int * prt = NULL;
    Sort(prt,10);
    return 0;
}

在这里插入图片描述

很显然,在_exit的表现中,“函数发生错误”还在缓冲区中未刷新,进程就已经退出了。

  • exit最后其实也会调用_exit, 但在调用_exit之前,还做了其他工作:
    1. 执行用户通过 atexiton_exit定义的清理函数;
    2. 关闭所有打开的流,所有的缓存数据均被写入;
    3. 调用_exit;

在这里插入图片描述

🐔异常终止

除了程序自己发现异常而终止,一个程序还可能因为外力因素而提前终止。例如我们之前学习过的指令:kill -9 ,它的作用就是从外部“杀”掉进程、或者我们经常使用的Ctrl+c

$ kill -9 进程ID

🐧进程等待

🐦必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏

  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程;

  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如:子进程运行完成,结果对还是不对,或者是否正常退出;

  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息;

🐦是什么

我们谈进程等待,那么我们究竟要等待什么呢?

  • 进程等待,就是通过系统调用,获取子进程的退出码或者退出信号的方式,顺便解决内存释放的问题。

🐦如何等待

这里就要介绍两个系统调用接口了:

  • wait
pid_t wait(int *status);
  • 返回值:成功返回被等待进程pid,失败返回-1

  • 参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

🔔解决子进程僵尸问题

之前讲到过,由于父进程为对子进程进行等待,子进程就会进入僵尸状态,且危害极大。那么意味着,要避免僵尸问题,我们必须对子进程进行等待。代码如下:

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

int main()
{   
    pid_t id = fork();

    int cnt = 50;
    if(id == 0)
    {
      // 子进程
      while(cnt)
      {
        printf("我是子进程,我还在运行,我还有%d...\n",cnt--);
        sleep(1);
      }
      exit(111);
    }
    // 父进程
    pid_t ret_id = wait(NULL);
    printf("%d %d\n",id,ret_id);
    sleep(10);
}

在这里插入图片描述

如图所示,在kill -9 终止子进程后,子进程并没有进入僵尸状态。而且可以清晰的看到,wait的返回值就是子进程的PID

🔔如何获取子进程status

在这里插入图片描述

  • waitwaitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

  • 如果传递NULL,表示不关心子进程的退出状态信息。

  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

  • status不能简单的当作整型来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

在这里插入图片描述

做个形象的比喻,如果你正在考试,如果你以正确的方式参加了考试,那么你的成绩必定有好有坏,就看你如何看待自己考试的结果了;但是,如果你考试作弊了,考试还未结束,你就被监考老师叉出去了,提前结束了考试,此时,你的卷子尽管可能有得分,这个得分有没有参考价值呢?当然没有。

所以,如果一个进程正常终止,我们可以拿到它的退出状态即进程退出码;如果一个进程被信号(监考老师)终止,此时退出状态是没有意义的,但是我们可以查看终止信号,(至少看看是什么原因导致的考试异常结束)。

示例

  • 进程正常终止;
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{   
    pid_t id = fork();

    int cnt = 5;
    if(id == 0)
    {
      // 子进程
      while(cnt)
      {
        printf("我是子进程,我还在运行,我还有%d...\n",cnt--);
        sleep(1);
      }
      exit(111);
    }
    // 父进程
    int status = 0;
    pid_t ret_id = wait(&status);
    printf("child exit code:%d,child exit singl:%d\n",(status >> 8) & 0xFF,status & 0x7F);
}

在这里插入图片描述

  • kill -9 终止进程(把计时改为20,因为我手速没那么快…);

在这里插入图片描述


  • waitpid;
pid_ t waitpid(pid_t pid, int *status, int options);
  • 返回值:

    • 当正常返回的时候waitpid返回收集到的子进程的PID
    • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
    • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
  • 参数:

    • pid
      • Pid = -1,等待任意一个子进程,与wait等效。
      • Pid > 0,等待其进程IDpid相等的子进程。
    • status
      • WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
      • WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options:

    • WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID

waitpidwait的用法大致是类似的,这里就不做专门的演示了。

🦋阻塞等待

仔细观察上文中示例中的结果,父进程的输出总在子进程结束之后,如图:

在这里插入图片描述

在子进程运行期间,父进程一直在等待,并没有做其他事情,直到等待子进程成功。我们把这种情况称为父进程在进行阻塞等待

在这里插入图片描述

如果父进程不想干干地等待子进程结束,而是想在等待的期间做点其他有意义的事情该如何处理呢?

🦋非阻塞等待

  • 首先我们先预设一批任务,在父进程等待期间执行这些任务;
void sync_disk()
{
  printf("这是一个刷新数据的任务\n");
}

void sync_log()
{
  printf("这是一个同步日志的任务\n");
}

void net_send()
{
  printf("这是一个网络发送的任务\n");
}
  • 设置任务加载函数、任务运行函数、任务初始化函数等;
#define TASK_NUM 10 // 任务的数量

typedef void (*FUNC_PTR)(); // 函数指针类型重定义

FUNC_PTR other_task[TASK_NUM]={NULL}; // 定义一个函数指针数组

int LoadTask(FUNC_PTR func)
{
  int i = 1;
  for(; i < TASK_NUM; ++i)
  {
    if(other_task[i]==NULL) break;
  }
  
  if(i == TASK_NUM) return -1;
  else other_task[i] = func;

  return 0;
}

void InitTask()
{
  int i = 0;
  for(; i < TASK_NUM; ++i) other_task[i] == NULL;

  LoadTask(sync_disk);
  LoadTask(sync_log);
  LoadTask(net_send);
}

void RunTask()
{
  for(int i = 0; i < TASK_NUM; i++)
  {
    if(other_task[i] == NULL) continue;
    other_task[i]();
  }
}
  • main函数设计;
int main()
{   
    pid_t id = fork();

    int cnt = 15;
    if(id == 0)
    {
      // 子进程
      while(cnt)
      {
        printf("我是子进程,我还在运行,我还有%d...\n",cnt--);
        sleep(1);
      }
      exit(111);
    }

    InitTask();
    // 父进程
    while(1)
    {
      int status = 0;
      pid_t ret_id = waitpid(id,&status,WNOHANG);
      if(ret_id < 0)
      {
        printf("等待出错\n");
        exit(1);
      }
      else if(ret_id == 0) // 子进程还未结束,做做其他事情
      {
        RunTask();
        sleep(1);
        continue;
      }
      else // 已等待成功 
      {
        if(WIFEXITED(status)) // 子进程正常退出
        {
          printf("等待成功,子进程pid:%d,子进程退出码:%d\n",id,WEXITSTATUS(status));
        }
        else // 子进程异常退出
        {
          printf("等待成功,子进程pid:%d,子进程退出信号:%d\n",id,status&0x7F);
        }

        break;
      }
    }
}

效果如下

在这里插入图片描述

🐦完整代码

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

#define TASK_NUM 10 // 任务的数量

void sync_disk()
{
  printf("这是一个刷新数据的任务\n");
}

void sync_log()
{
  printf("这是一个同步日志的任务\n");
}

void net_send()
{
  printf("这是一个网络发送的任务\n");
}

typedef void (*FUNC_PTR)(); // 函数指针类型重定义

FUNC_PTR other_task[TASK_NUM]={NULL};

int LoadTask(FUNC_PTR func)
{
  int i = 1;
  for(; i < TASK_NUM; ++i)
  {
    if(other_task[i]==NULL) break;
  }
  
  if(i == TASK_NUM) return -1;
  else other_task[i] = func;

  return 0;
}

void InitTask()
{
  int i = 0;
  for(; i < TASK_NUM; ++i) other_task[i] == NULL;

  LoadTask(sync_disk);
  LoadTask(sync_log);
  LoadTask(net_send);
}

void RunTask()
{
  for(int i = 0; i < TASK_NUM; i++)
  {
    if(other_task[i] == NULL) continue;
    other_task[i]();
  }
}

int main()
{   
    pid_t id = fork();

    int cnt = 15;
    if(id == 0)
    {
      // 子进程
      while(cnt)
      {
        printf("我是子进程,我还在运行,我还有%d...\n",cnt--);
        sleep(1);
      }
      exit(111);
    }

    InitTask();
    // 父进程
    while(1)
    {
      int status = 0;
      pid_t ret_id = waitpid(id,&status,WNOHANG);
      if(ret_id < 0)
      {
        printf("等待出错\n");
        exit(1);
      }
      else if(ret_id == 0) // 子进程还未结束,做做其他事情
      {
        RunTask();
        sleep(1);
        continue;
      }
      else // 已等待成功 
      {
        if(WIFEXITED(status)) // 子进程正常退出
        {
          printf("等待成功,子进程pid:%d,子进程退出码:%d\n",id,WEXITSTATUS(status));
        }
        else // 子进程异常退出
        {
          printf("等待成功,子进程pid:%d,子进程退出信号:%d\n",id,status&0x7F);
        }

        break;
      }
    }
}

本章的内容就到这里了,觉得对你有帮助的话就支持一下博主把~

在这里插入图片描述

点击下方个人名片,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/559428.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

00后实在太卷了,跳槽到我们公司起薪20k,都快超过我了....

都说00后已经躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。 前段时间我们部门就来了个00后&#xff0c;工作都还没两年&#xff0c;跳到我们公司起薪20K&#xff0c;都快接近我了。 后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。最近和…

美股股指期货是什么?有哪些美股股指期货交易门槛?

美股期指就是美股期货指数&#xff0c;并不是单独的指道琼斯还是纳斯达克&#xff0c;就好像中国的股指期货一样道理&#xff0c;是以沪深300为标的的&#xff0c;美股期指也是期货&#xff0c;不是单纯的指数。交易美股产品需要先完成开户&#xff0c;要想一次性开户成功就要满…

2023.4月及5月最新HCIP 考试战报来袭

2023年HCIP/CCNP考试战报_微思xmws的博客-CSDN博客国内企业或国企、事业单位&#xff0c;华为设备普及&#xff0c;国内未来发展趋势&#xff0c;考华为认证更被认可。如果你在外企上班或有出国打算&#xff0c;推荐考思科。https://blog.csdn.net/XMWS_IT/article/details/129…

【隔空投送】2023新品,加速科技ST2500E重磅来袭

随着5G通信、人工智能、虚拟现实、新能源以及先进封装技术的飞速发展&#xff0c;芯片线宽尺寸不断减小&#xff0c;芯片复杂度不断跃升&#xff0c;对测试机的要求愈加提高。提供多种测试程序并可进行大量的并行测试&#xff0c;有效减少测试成本&#xff0c;缩短产品上市时间…

Paddle 模型转 TensorRT加速模型

Paddle 模型转 TensorRT加速模型 概述 NVIDIA TensorRT 是一个高性能的深度学习预测库&#xff0c;可为深度学习推理应用程序提供低延迟和高吞吐量。PaddlePaddle 采用子图的形式对TensorRT进行了集成&#xff0c;即我们可以使用该模块来提升Paddle模型的预测性能。在这篇文章…

Ubuntu显示时间不正确的问题解决

安装完ubuntu后&#xff0c;发现我的ubuntu显示的时间不对&#xff0c;和网络的时间根本对不上。网上查找了相关设置方法&#xff0c;这里总结一下。 一般来说&#xff0c;显示时间不正确的情况就两种情况&#xff0c;第一&#xff1a;是你系统设置的时区不对&#xff1b;第二…

多层感知机学习XOR实例

多层感知机学习XOR实例 多层感知机介绍使用多层感知机学习XOR实例传统统计数学方法&#xff08;传统机器学习&#xff09;使用多层感知机学习XOR 总结 多层感知机介绍 多层感知机&#xff08;Multilayer Perceptron&#xff0c;MLP&#xff09;&#xff0c;又称为深度前馈网络…

Compose 二三事:绘制原理

setContent做了什么 我们基于一个最简单的例子进行分析 class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Text(text "Hello World!")}} }这里setContent做了什么…

【Jmeter入门】使用Jmeter做接口测试

目录 一、Jmeter简介 二、Jmeter相关插件安装&#xff1a; 三、Jmeter运行模式及参数 四、Jmeter函数 五、Jmeter属性与变量 六、Jmeter如何做接口测试 【1】、Jmeter 的使用步骤 【2】、接口请求实例 1. “用户定义的变量” 的应用 3. 建立接口间的关联 4…

Gaussian量子化学计算、LAMMPS分子动力学模拟

Gaussian是做半经验计算和从头计算使用最广泛的量子化学软件&#xff0c;可研究诸如分子轨道&#xff0c;结构优化&#xff0c;过渡态搜索&#xff0c;热力学性质&#xff0c;偶极矩和多极矩&#xff0c;电子密度和电势&#xff0c;极化率和超极化率&#xff0c;红外和拉曼光谱…

超越竞争的获客之道:DTC品牌出海策略全面解析

随着全球数字化的快速发展&#xff0c;DTC品牌正迎来一个全新的时代。然而&#xff0c;随着越来越多的DTC品牌进入国际市场&#xff0c;如何在激烈的竞争中脱颖而出&#xff0c;并获得新客户成为一个关键的挑战。本文Nox聚星将和大家深入探讨DTC品牌在出海时代如何破解获客困局…

外包干了五年,废了...

先说一下自己的情况。大专生&#xff0c;17年通过校招进入湖南某软件公司&#xff0c;干了接近5年的测试点点点&#xff0c;今年年上旬&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了五年的点工…

10年开发,浅谈eolink 、aifox、apipost 横向对比

功能对比 在实际工作中,eolink 、apifox、apipost 三个工具,我个人都有使用。接下来,我会对三款功能对比,于是我拉了个功能对比的清单。 特别说明:以下的对比,不吹不黑,只列功能,纯客观比对,不带有任何商业带货的意义。 eolink 、aifox、apipost 三款工具有很多功能模块,本次仅…

acwing提高——DFS之连通性问题+搜索顺序

1 连通性问题&#xff08;内部搜索&#xff09; 内部搜索一般不用恢复现场 1.迷宫 题目http://ybt.ssoier.cn:8088/problem_show.php?pid1215 #include<bits/stdc.h> using namespace std; const int N110; bool st[N][N]; char g[N][N]; int n; int sx,sy,ex,ey; …

95后字节八年测开晒出工资单:狠补了这个,真香···

最近一哥们跟我聊天装逼&#xff0c;说他最近从字节跳槽了&#xff0c;我问他跳出来拿了多少&#xff1f;哥们表示很得意&#xff0c;说跳槽到新公司一个月后发了工资&#xff0c;月入5万多&#xff0c;表示很满足&#xff01;这样的高薪资着实让人羡慕&#xff0c;我猜这是税后…

2022年深圳杯数学建模B题基于用电可靠性的配电网规划解题全过程文档及程序

2022年深圳杯数学建模 B题 基于用电可靠性的配电网规划 原题再现&#xff1a; 如果一批用户变压器&#xff08;下面简称用户&#xff09;仅由一个电源变电站&#xff08;下面简称电源&#xff09;供电&#xff0c;称为单供。这时配电网由电线和开关联接成以电源为根节点的树状…

k8s使用ECK形式部署elasticsearch+kibana

文章目录 前言一、ECK是什么&#xff1f;二、安装ECK1.crd.yaml2.operator.yaml 三、安装es&#xff0c;elasticsearch-cluster.yaml四、安装kibana总结 前言 在k8s上基于ECK&#xff08;2.4&#xff09;部署elasticsearch&#xff0c;简单记录一下&#xff0c;主要是quicksta…

《程序员面试金典(第6版)》面试题 02.03. 删除中间节点(特殊的删除节点操作)

题目描述 若链表中的某个节点&#xff0c;既不是链表头节点&#xff0c;也不是链表尾节点&#xff0c;则称其为该链表的「中间节点」。 题目传送门&#xff1a;面试题 02.03. 删除中间节点 假定已知链表的某一个中间节点&#xff0c;请实现一种算法&#xff0c;将该节点从链表中…

服务(第二十九篇)zabbix

zabbix 是什么&#xff1f; zabbix 是一个基于 Web 界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。 zabbix 能监视各种网络参数&#xff0c;保证服务器系统的安全运营&#xff1b;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。 zabbi…

太阳升起和落下(长文警告)

今天分享一个太阳升起落下的动画场景。 有朋友问我为什么只发代码不做说明&#xff0c;今天我们尝试下对代码进行注解说明一下。 首先这个场景大致的可拆分为4个部分&#xff1a;太阳/月亮&#xff0c;右下角的按钮&#xff0c;天上的云和最显眼的建筑。 我们先做一个按钮&am…