Linux | 进程终止与进程等待

news2024/11/18 9:29:35

目录

前言

一、进程终止

1、进程终止的几种可能 

2、exit 与 _exit

二、进程等待

1、为什么要进程等待

2、如何进行进程等待

(1)wait函数 

(2)waitpid函数

3、再次深刻理解进程等待


前言

        我们前面介绍进程时说子进程退出,父进程不对子进程进行资源回收,子进程会进入僵尸状态,对于操作系统来说,这是一种资源泄漏,而且还是操作系统层面的资源泄漏,除非父进程退出,否则子进程将一直处于僵尸状态,本章就介绍父进程如何回收子进程;

一、进程终止

1、进程终止的几种可能 

        在介绍回收子进程之前,我们必须对进程终止有一定的了解;所谓进程终止,就是进程的退出,这里我们首先要直到进程退出有以下三种可能;

1、程序正常运行结束,结果正确;

2、程序正常运行结束,结果不正确;

3、程序崩溃,结果不重要;

        可能我们之前对进程退出并没有什么概念,我们是如何区分以上三种情况呢?比如我们以前在virtual studio上运行我们的C/C++程序,我们总会写一个main函数,而一般我们都会在main函数的最后写一个return 0;实际上,这个0就是退出码,我们通过这个判定结果是否正确,退出码有对应的解释含义,我们可以通过 strerror 函数将退出码含义打印出来;这是区分情况一和情况二的方法,对于情况三,程序崩溃,我们的软件 virtual studio 一般会出来一个弹窗,告诉你是哪里引发了程序的崩溃,最常见的就是除零错误、空指针解引用等等,都会导致程序崩溃;下面我们来打印退出码;如下代码;

        我们编译运行上述程序,结果如下;

        我们发现错误码的信息最多编辑到了133号,前面几项我们也很熟悉,其中第一项0就是成功且结果正确;

2、exit 与 _exit

        前面我们说过,在main函数中,我们可以通过return语句让进程退出,并返回返回值;那么要是我们不在main函数呢?那么我们难道要返回main函数再调用return语句?那也太麻烦了吧,实际上,我们也可以通过exit函数和 _ exit函数来使进程终止;如下代码;

        我们编译代码,结果如下所示;这里介绍一条命令 echo $?;可以查看最近运行的一个程序的返回值,我们发现我们输入除-1以外的值时,返回值为0,也就是main函数中的return 语句,而我们输入-1时,返回值为14,是我们调用exit函数的返回值;

        上述的退出函数exit换成_exit也可以实现相同功能,那么其区别在哪呢?看如下代码;

        当我们使用exit函数后,运行结果如下;

        当我们使用_exit函数后,运行结果如下;

        我们已经看不到you can see me;这是因为我们的打印的内容还在C语言的缓冲区内,而我们的_exit是系统调用,在退出前并不能刷新缓冲区;而我们的exit为C语言库函数,会刷新我们的缓冲区;

补充:C语言的缓冲区刷新机制为行刷新,因为在打印时没有换行符,所以我们的使用exit函数时会打印,而使用_exit函数时不会打印;

二、进程等待

1、为什么要进程等待

        其一,这个原因早在我们前面就已经进行了阐述,当子进程退出时,父进程不对子进程进行资源回收,子进程将一直处于僵尸状态,而这种状态会造成系统资源泄漏,这时我们需要通过进程等待的方式,给子进程 “收尸” ;

        其二,我们让子进程去完成任务是否需要子进程完成的如何?对于某些时候,就有这样的需求,因此进程等待另一作用是获取子进程任务完成情况;

2、如何进行进程等待

(1)wait函数 

        关于如何进行进程等待,我们通常是通过系统调用wait和waitpid来实现;我们首先看啊可能wait的函数声明;

        这个函数只有一个参数,是一个输出型参数,所谓输出型参数就是我们传一个指针,函数内会给这个指针执行的值进行赋值返还给我们;这个输出型参数就是子进程的退出码和终止信号,这里我们暂时设置为NULL,等待会介绍waitpid时再做介绍;

        该函数的返回值,若调用成功,返回子进程pid,若失败返回-1.错误码被设置,我们写出如下代码;查看父进程是否回收了子进程;

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

int main()
{
    int id = fork();
    if(id == -1)
    {
        // fork调用失败
        exit(1);
    }
    else if(id == 0)
    {
        // 子进程
        int cnt = 5;
        while(cnt--)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(14); // 退出码
    }
    else  
    {
        // 父进程
        sleep(7);
        wait(NULL); // 进程等待
        sleep(2);
    }
    return 0;
}

        我们再在命令行输入以下脚本命令对这两个进程进行监控;

while :; do ps -axj | head -1 && ps -axj | grep test | grep -v grep; sleep 1; echo "-------------------"; done

        我们发现前面几秒确实都在运行,接着中间有两秒子进程处于僵尸状态,因为父进程比子进程多sleep两秒,正如我们所料;接着最后只剩父进程;子进程成功被父进程回收;

(2)waitpid函数

        下面为我们通过man手册查询结果;

参数pid:

pid作用
pid < -1等待进程组号为pid绝对值的任何子进程。
pid = -1等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。
pid = 0等待进程组号与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。
pid > 0等待进程号为pid的子进程。

 

注意:这里进程组号的暂不提及,我们用的也不多,平常用的较多的就是2和4;

参数status:

        这个参数为输出型参数,与我们wait函数相同;关于这个参数的使用,我们不能将里面的int值整体使用,得按比特位分开使用;

情况一:正常退出

        此时,我们使用第7到第15比特位,当作退出码;我们可以通过 (status >> 8) & 0xFF来获得这个退出码,还可以使用宏函数 WEXITSTATUS 来获取,通过宏函数 WIFEXITED 来获取进程是否正常退出,若正常退出返回真,否则返回假; 

情况二:信号终止退出(异常退出)

        这里我们用后面0到6的比特位来表示信号终止,我们可以使用 status & 0x7F来得到这个终止信号;至于这里的 core dump 标志位暂不讲解,这又是另一个话题了;

参数options:

        这个参数默认填0就好,表示阻塞等待,若填WNOHANG,则表示非阻塞等待;

返回值:

waitpid的返回值略比wait复杂一些,有三种情况;

1、正常返回,此时返回子进程pid;

2、若设置WNOHANG,且子进程还未退出,则返回0,子进程退出了,返回子进程pid;

3、调用失败,返回-1,错误码被设置;

代码实践:

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

int main()
{
    int id = fork();
    if(id == -1)
    {
        // fork调用失败
        exit(1);
    }
    else if(id == 0)
    {
        // 子进程
        int cnt = 5;
        while(cnt--)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(14); // 退出码
    }
    else  
    {
        // 父进程
        sleep(7);
        int status = 0;
        // 进程等待
        int ret = waitpid(id, &status, 0);  // 此时与我们的wait函数功能相同
        if(WIFEXITED(status))
        {
            printf("子进程正常退出,退出码为%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("子进程异常退出,收到信号%d\n", (status) & 0x7F);
        }
        sleep(2);
    }
    return 0;
}

        代码输出结果如下;

        若我们将代码加上一个除零错误;

        运行结果如下;

        我们再通过kill -l查看信号;8号信号正是我们的浮点数计算问题;

        我们再将代码改一下,将我们的waitpid改成非阻塞等待的情况,如下;

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

int main()
{
    int id = fork();
    if(id == -1)
    {
        // fork调用失败
        exit(1);
    }
    else if(id == 0)
    {
        // 子进程
        int cnt = 5;
        // 除零错误展示
        // int a = 10/ 0;
        while(cnt--)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(14); // 退出码
    }
    else  
    {
        // 父进程
        int status = 0;
        // 进程等待
        while(true)
        {
            int ret = waitpid(id, &status, WNOHANG); 
            if(ret == -1)
            {
                printf("waitpid调用失败\n");
                exit(-1);
            }
            else if(ret == 0)
            {
                printf("子进程还未退出,我再干点别的\n");
                sleep(1);
            }
            else
            {
                printf("等待成功\n");
                break;
            }
        }
        
        if(WIFEXITED(status))
        {
            printf("子进程正常退出,退出码为%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("子进程异常退出,收到信号%d\n", (status) & 0x7F);
        }
        sleep(2);
    }
    return 0;
}

        运行结果如下,此时我们的父进程就不用阻塞等待子进程结束了,父进程只需要通过轮询的方式来来回收子进程;

3、再次深刻理解进程等待

        上述内容为进程等待的实操部分,我们现在再次回到理论部分,我有如下问题;

问题一:我们是否可以通过一个全局变量来获取子进程的退出码呢?

        不可以,虽然父进程和子进程共用一段代码,但是都有各自的进程地址空间,当我们使用全局变量时,子进程往这个全局变量里写入时,会发生写时拷贝,因此无法获得退出码,这也是进程的独立性;

问题二:既然进程具有独立性,那么wait和waitpid是如何获取子进程的退出码的呢?

        我们的wait和waitpid属于系统调用,既然是系统调用,当然是属于操作系统的一部分,我们在回收子进程时,实际上是销毁进程PCB等内核数据的过程,而PCB(task_struct)中有一个退出码和退出信号的字段,以下为Linux源码截图;

        我们在task_struct里确实发现了这几个字段,如果有兴趣的,可以去官网下载一份源码,task_strcut结构体在 include/linux/sche.h 中;

        回到正题,既然我们task_struct中有这些字段,那么我们父进程回收子进程的时候是否可以获取这些字段的信息呢?答案当然是肯定的,我们的wait和waitpid为系统调用,当然有资格获取这些字段,我们的父进程也就可以通过这两个系统调用拿到了子进程的退出码了;

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

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

相关文章

Canal整合SpringBoot详解(二)

文章目录 Canal整合SpringBoot详解&#xff08;二&#xff09;什么是canal案例2&#xff1a;CanalKafka实现mysql和elasticsearch的数据同步⭐Docker搭建elasticsearch7.8.0&#xff08;单机版本&#xff09;⭐Docker安装elasticsearch-head5⭐解决es-head 406错误问题直接修改…

实用篇-Eureka注册中心

一、提供者与消费者 服务提供者&#xff1a;一次业务中&#xff0c;被其他微服务调用的服务。(提供接口给其他微服务) 服务消费者&#xff1a;一次业务中&#xff0c;调用其他微服务的服务。(调用其他微服务提供的接口) 例如前面的案例中&#xff0c;order-service微服务是服…

系列七、动态代理

一、概述 二、Jdk动态代理案例 2.1、Star /*** Author : 一叶浮萍归大海* Date: 2023/10/27 17:16* Description:*/ public interface Star {/*** 唱歌* param name 歌曲名字* return*/String sing(String name);/*** 跳舞*/void dance(); } 2.2、BigStar /*** Author : 一叶…

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)

&#xff08;1&#xff09;acwing 4557. 最长上升子序列 4557. 最长上升子序列 - AcWing题库 给定一个长度为 N 的整数序列 a1,a2,…,aN。请你计算该序列的最长上升子序列的长度。上升子序列是指数值严格单调递增的子序列 输入格式 第一行包含整数 N第二行包含 N个整数 a1,a…

LLM系列 | 23:多模态大模型:浦语·灵笔InternLM-XComposer解读、实战和思考

引言 ​简介 模型解读 模型架构 训练 实战 环境准备 本地实测 服务部署 总结 引言 谁念西风独自凉&#xff0c;萧萧黄叶闭疏窗&#xff0c;沉思往事立残阳。 Created by DALLE 3 小伙伴们好&#xff0c;我是《小窗幽记机器学习》的小编&#xff1a;卖热干面的小女孩…

在Golang中理解错误处理

处理Golang中临时错误和最终错误的策略和示例 作为一名精通Golang的开发人员&#xff0c;您了解有效的错误处理是编写健壮可靠软件的关键因素。在复杂系统中&#xff0c;错误可能采取各种形式&#xff0c;包括临时故障和最终失败。在本文中&#xff0c;我们将探讨处理Golang中…

源码解析SpringMVC之RequestMapping注解原理

1、启动初始化 核心&#xff1a;得到应用上下文中存在的全部bean后依次遍历&#xff0c;分析每一个目标handler & 目标方法存在的注解RequestMapping&#xff0c;将其相关属性封装为实例RequestMappingInfo。最终将 uri & handler 之间的映射关系维护在类AbstractHand…

Java入门篇 之 数据类型(简单介绍)

博主回归学习状态的第三篇文章&#xff0c;希望对大家有所帮助 今日份励志文案:你若决定灿烂&#xff0c;山无遮&#xff0c;海无拦 加油&#xff01; Java中一共存在2种数据类型 1 . 基本数据类型,基本数据类型四种和八种之说(具体看下图) 四种说的是&#xff0c;整数型&…

vscode打开settings.json方法

cmd shift p&#xff0c;输入setting Open Workspace Settings 也会打开UI设置界面&#xff1b; Open User Settings (JSON) 会打开用户设置 settings.json 文件&#xff1b; Open Workspace Settings (JSON) 会打开工作区设置 settings.json 文件 vscode存在两种设置 sett…

损失函数和目标函数|知识补充

这张图中&#xff0c;横坐标size表示房屋的大小&#xff0c;纵坐标price表示房屋的价格&#xff0c;现在需要建立模型来表示两者之间的关系。 对于给定的输入x&#xff0c;模型会有一个输出f(x)&#xff0c;用一个函数来度量拟合的程度&#xff0c;也就是真实值和预测值之间的…

前端工程化面试题及答案【集合】

前言&#xff1a; 欢迎浏览和关注本专栏《 前端就业宝典 》&#xff0c; 不管是扭螺丝还是造火箭&#xff0c; 多学点知识总没错。 这个专栏是扭螺丝之上要造火箭级别的知识&#xff0c;会给前端工作学习的小伙伴带来意想不到的帮助。 本专栏将前端知识拆整为零&#xff0c;主要…

工业相机常见的工作模式、触发方式

参考&#xff1a;机器视觉——工业相机的触发应用(1) - 知乎 工业相机常见的工作模式一般分为&#xff1a; 触发模式连续模式同步模式授时同步模式 触发模式&#xff1a;相机收到外部的触发命令后&#xff0c;开始按照约定时长进行曝光&#xff0c;曝光结束后输出一帧图像。…

子集生成算法:给定一个集合,枚举所有可能的子集

给定一个集合&#xff0c;枚举所有可能的子集。 &#xff08;为简单起见&#xff0c;本文讨论的集合中没有重复元素&#xff09; 1、方法一&#xff1a;增量构造法 第一种思路是一次选出一个元素放到集合中&#xff0c;程序如下&#xff1a; void print_subset(int n, int …

C++系列之list的模拟实现

&#x1f497; &#x1f497; 博客:小怡同学 &#x1f497; &#x1f497; 个人简介:编程小萌新 &#x1f497; &#x1f497; 如果博客对大家有用的话&#xff0c;请点赞关注再收藏 &#x1f31e; list的节点类 template struct list_Node { public: list_Node* _prev; list_…

Tomcat服务部署和优化

目录 一、Tomcat&#xff1a; 1、Tomcat作用&#xff1a; 2、Tomcat的核心组件&#xff1a; 3、servlet作用&#xff1a; 4、Tomcat的核心功能&#xff1a; 二、tomcat配置 一、Tomcat&#xff1a; 是一个开源的web应用服务器&#xff0c;nginx主要处理静态页面&#xff…

不再受害:如何预防和应对.mallab勒索病毒攻击

导言&#xff1a; 我们的数据成了我们的珍宝&#xff0c;但也成了黑客们追逐的目标。其中&#xff0c;.mallab勒索病毒就是一个充满阴谋和神秘的数字威胁&#xff0c;它采用高度复杂的方法将您的数据锁在数字牢笼中。本文91数据恢复将深入探讨.mallab勒索病毒的起源、工作方式…

【RabbitMQ 实战】12 镜像队列

一、镜像队列的概念 RabbitMQ的镜像队列是将消息副本存储在一组节点上&#xff0c;以提高可用性和可靠性。镜像队列将队列中的消息复制到一个或多个其他节点上&#xff0c;并使这些节点上的队列保持同步。当一个节点失败时&#xff0c;其他节点上的队列不受影响&#xff0c;因…

视频转换器WinX HD Video Converter mac中文特点介绍

WinX HD Video Converter mac是一款功能强大的视频转换器&#xff0c;它可以将各种不同格式的视频文件转换为其他视频格式&#xff0c;以便用户在各种设备上进行播放。WinX HD Video Converter是一个功能强大、易于使用的视频转换器&#xff0c;适用于各种类型的用户&#xff0…

可图性判断(图论)

如图所示&#xff1a; 1.去arr[i]首元素&#xff0c; 后面arr[i]个元素减一 2.排序&#xff0c;以此类推 3.最后如果出现负数则不可图 4.最后元素为0&#xff0c;则可图 问题 L: Degree Sequence of Graph G代码如下&#xff1a;

C#版字节跳动SDK - SKIT.FlurlHttpClient.ByteDance

前言 在我们日常开发工作中对接第三方开放平台&#xff0c;找一款封装完善且全面的SDK能够大大的简化我们的开发难度和提高工作效率。今天给大家推荐一款C#开源、功能完善的字节跳动SDK&#xff1a;SKIT.FlurlHttpClient.ByteDance。 项目官方介绍 可能是全网唯一的 C# 版字节…