Linux 进程终止和进程等待

目录

0.前言

1. 进程终止

1.1 进程退出的场景

1.2 进程常见退出方法

1.2.1 正常退出

1.2.2 异常退出

2. 进程等待

2.1 进程等待的重要性

2.2 进程等待的方法

2.2.1 wait() 方法

2.2.2 waitpid() 方法

2.3 获取子进程 status

2.4 阻塞等待和非阻塞等待

2.4.1 阻塞等待

2.4.2 非阻塞等待

3.结语


(图像由AI生成) 

0.前言

在上一个博客中,我们介绍了如何通过 fork() 函数创建子进程。子进程创建后,通常会执行一些任务,然后终止。而父进程在子进程终止后,需要适当的方式来处理和等待子进程的退出。本文将详细讨论 Linux 中进程的终止与进程等待的相关内容。

1. 进程终止

进程终止是操作系统管理进程生命周期的重要阶段,当进程完成其预定的任务或遇到意外时,它会终止并向系统报告其退出状态。理解进程如何正常或异常终止,对于开发人员和系统管理员进行进程管理至关重要。

1.1 进程退出的场景

进程的退出场景可以大致归纳为以下三种:

  • 代码运行完毕,结果正确:进程执行完所有任务并成功返回预期结果。
  • 代码运行完毕,结果不正确:进程虽然执行结束,但由于逻辑错误或其他原因导致输出结果与预期不符。
  • 代码异常终止:进程在执行过程中发生了未预期的错误,导致进程崩溃或被系统强制终止。

虽然进程退出的原因和场景各异,但所有进程最终都会通过一定的机制向系统报告其结束状态。

1.2 进程常见退出方法

进程退出的方式大致分为两类:正常退出和异常退出。我们可以通过不同的系统调用或外部信号来结束进程。

1.2.1 正常退出

在进程正常退出的情况下,它的生命周期如预期完成,并返回特定的状态码,表示程序执行成功或失败。常见的正常退出方法如下:

  1. main() 函数返回: 在 C/C++ 语言中,进程的入口点是 main() 函数。当程序执行完毕并到达 main() 的结束处,进程会通过 return 语句返回一个状态码,向系统报告其退出状态。典型地,return 0 表示正常退出,而非零值(如 return 1return -1)表示出现了某些错误。

    int main() {
        // ... 业务逻辑
        return 0;  // 正常退出
    }
    
  2. 调用 exit()exit()标准库函数,允许程序随时结束执行,并返回状态码。调用 exit() 后,程序会执行清理操作,例如关闭打开的文件、释放资源,并调用通过 atexit() 注册的回调函数。exit() 通常用于程序需要在特定条件下主动退出时。

    #include <stdlib.h>
    int main() {
        // ... 业务逻辑
        if (某种错误发生) {
            exit(1);  // 异常退出
        }
        exit(0);  // 正常退出
    }
    
  3. 调用 _exit()_exit()系统调用,通常在子进程中使用。与 exit() 不同,_exit() 不会执行缓冲区的刷新或已注册的清理函数,它直接向内核报告进程结束并释放其资源。通常在子进程完成其工作后,调用 _exit() 立即退出。

    #include <unistd.h>
    int main() {
        if (fork() == 0) {
            // 子进程执行
            _exit(0);  // 立即退出
        }
        // 父进程继续执行
        return 0;
    }
    

1.2.2 异常退出

异常退出是指进程在非预期情况下由于错误或外部干预而终止。常见的异常退出方式包括:

  1. Ctrl+C(信号终止): 当用户在命令行按下 Ctrl+C,系统会发送 SIGINT 信号给进程,指示其立即终止。这是一种外部干预的方式,常用于终止长时间运行的任务。

    ./your_program
    # 用户按下 Ctrl+C,程序收到 SIGINT 信号并终止
    
  2. 异常信号终止: 进程可能由于内部错误(如访问无效内存地址)而收到操作系统发送的异常信号,导致进程非正常退出。常见的异常信号包括 SIGSEGV(段错误)、SIGFPE(算术错误,如除零)等。

    例如,非法内存访问会导致 SIGSEGV 信号:

    int main() {
        int *ptr = NULL;
        *ptr = 42;  // 导致段错误,异常退出
        return 0;
    }
    

当进程因信号终止时,系统会向父进程报告该终止信号,而不是正常的退出状态码。开发者可以通过适当的信号处理机制捕捉并处理这些信号,避免进程非预期崩溃。

2. 进程等待

当子进程终止时,父进程需要进行适当的处理,避免出现僵尸进程。僵尸进程不仅会占用系统的进程表条目,还会导致内存资源无法及时回收。因此,父进程通过进程等待机制来回收子进程资源,并获取子进程的退出状态。

2.1 进程等待的重要性

当子进程结束后,如果父进程不主动等待并回收子进程资源,就可能导致子进程进入僵尸状态。僵尸进程的特性是已经终止,但仍然在系统的进程表中保留一些信息,包括退出状态。由于这些进程已经结束,系统资源无法通过常规的手段释放。

  • 僵尸进程占用系统资源,并且无法被终止。即使使用 kill -9 这样的强制终止信号,也无法“杀死”一个已经处于僵尸状态的进程,因为它已然是“死去的进程”。
  • 另外,父进程往往需要知道子进程任务完成的情况,例如子进程是否正常退出,返回结果是否正确。这些信息对父进程判断后续操作具有重要意义。

因此,父进程通过等待机制能够:

  1. 回收子进程的资源,避免僵尸进程;
  2. 获取子进程的退出状态,了解子进程的执行结果。

2.2 进程等待的方法

Linux 提供了几种等待子进程的方法,最常见的有 wait()waitpid() 函数。

2.2.1 wait() 方法

wait() 是一个简单的进程等待函数,父进程通过调用 wait() 可以阻塞自身,直到有一个子进程终止。它的基本使用方式如下:

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

pid_t wait(int *status);
  • 返回值:成功时,返回已终止的子进程的 PID;如果发生错误,返回 -1
  • 参数
    • status:指向一个整数的指针,用于存储子进程的退出状态。如果不关心子进程的退出状态,可以将该参数设置为 NULL

wait() 函数适用于父进程只需等待任意一个子进程退出的场景。当父进程有多个子进程时,wait() 将会等待其中的任何一个结束,并返回它的进程 ID。

2.2.2 waitpid() 方法

waitpid()wait() 的增强版本,提供了更多的功能和灵活性。例如,父进程可以通过 waitpid() 等待特定的子进程,或者选择非阻塞等待。其函数定义如下:

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

    • 如果成功,waitpid() 返回终止的子进程的 PID。
    • 如果设置了 WNOHANG 选项且没有任何子进程终止,返回 0
    • 如果发生错误,返回 -1,并设置 errno 以指示错误原因。
  • 参数

    • pid
      • pid = -1:等待任意子进程终止,功能与 wait() 相同。
      • pid > 0:等待指定 PID 的子进程终止。
    • status:与 wait() 中类似,保存子进程的退出状态。可以通过 WIFEXITED(status) 判断子进程是否正常终止,使用 WEXITSTATUS(status) 提取退出码。
    • options
      • WNOHANG:如果没有子进程终止,则 waitpid() 立即返回,而不会阻塞父进程。这对于父进程需要同时处理其他任务时非常有用。

2.3 获取子进程 status

在使用 wait()waitpid() 等待子进程时,除了能够回收子进程的资源,还可以通过 status 参数获取子进程的退出信息。这个参数是一个输出型参数,由操作系统填充,用来向父进程反馈子进程的退出状态。

status 参数的使用

  • status 的意义: 当我们调用 wait()waitpid() 时,status 参数是一个用于存储子进程退出状态的变量。如果我们不关心子进程的退出状态,可以将这个参数设置为 NULL。然而,如果我们希望获得子进程的退出信息,需要提供一个指针,操作系统会将退出信息写入该地址。

  • 位图解读status 参数并不是一个简单的整形值,而是一个位图,通常我们只需要关心它的低 16 位。其中,最常用的信息包括:

    • 子进程是否正常退出;
    • 子进程的退出码;
    • 如果是异常终止,是什么信号导致的异常终止。

当子进程正常退出时,status 中的高位(第 8 到第 15 位)存储了子进程的退出码,而低 7 位(第 0 到第 6 位)用于表示子进程的信号终止信息。

  • 正常退出:如果子进程是通过 exit()return 正常退出,status 的低 7 位应该是 0,表示没有通过信号终止。此时,高位存储的是子进程的退出码,可以通过 st >> 8 提取。

  • 异常终止:如果子进程因为信号而被终止,低 7 位会存储导致终止的信号编号。父进程可以通过 st & 0X7F 获取到信号编号,进一步判断子进程因何信号终止。

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

int main() {
    pid_t pid;
    if ( (pid=fork()) == -1 ) {
        perror("fork"), exit(1);
    }

    if (pid == 0) {
        // 子进程休眠20秒后正常退出,退出码为10
        sleep(20);
        exit(10);
    } else {
        // 父进程等待子进程退出
        int st;
        int ret = wait(&st);

        if (ret > 0 && (st & 0X7F) == 0) { // 正常退出
            printf("child exit code:%d\n", (st >> 8) & 0XFF);
        } else if (ret > 0) { // 异常退出
            printf("sig code : %d\n", st & 0X7F);
        }
    }
}

测试结果

  1. 当子进程正常退出时,输出如下:

     

    子进程正常退出,父进程通过 status 获取子进程的退出码为 10

  2. 当子进程在其他终端被 kill 掉时,输出如下:

     

    这是因为子进程被 SIGKILL 信号(编号为 9)终止,父进程通过 status 获取到了导致子进程终止的信号编号。

2.4 阻塞等待和非阻塞等待

在进程等待时,父进程可以选择采用阻塞等待或者非阻塞等待的方式来处理子进程的退出。阻塞等待会使父进程在子进程退出前一直处于等待状态,而非阻塞等待则允许父进程在子进程未退出时继续执行其他任务。

2.4.1 阻塞等待

阻塞等待是最常见的等待方式。当父进程调用 wait()waitpid() 并不设置任何非阻塞选项时,父进程会一直等待直到有子进程退出。此时,父进程会被阻塞,无法进行其他操作。

代码示例

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

int main() {
    pid_t pid = fork();  // 创建子进程

    if (pid == -1) {
        perror("fork failed");
        exit(1);
    }

    if (pid == 0) {  // 子进程
        printf("Child process running...\n");
        sleep(5);  // 子进程休眠5秒模拟任务执行
        printf("Child process finished.\n");
        exit(0);
    } else {  // 父进程
        int status;
        printf("Parent waiting for child to exit (blocking)...\n");
        wait(&status);  // 阻塞等待子进程结束
        printf("Child exited with status: %d\n", WEXITSTATUS(status));
    }

    return 0;
}

输出结果

  • 父进程在子进程运行期间被阻塞,直到子进程结束后才继续执行。
 

2.4.2 非阻塞等待

非阻塞等待允许父进程在等待子进程时继续执行其他任务,而不是阻塞等待子进程结束。通过在 waitpid() 中传入 WNOHANG 选项,父进程可以立即返回,即使子进程还没有结束。

代码示例

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

int main() {
    pid_t pid = fork();  // 创建子进程

    if (pid == -1) {
        perror("fork failed");
        exit(1);
    }

    if (pid == 0) {  // 子进程
        printf("Child process running...\n");
        sleep(5);  // 子进程休眠5秒模拟任务执行
        printf("Child process finished.\n");
        exit(0);
    } else {  // 父进程
        int status;
        printf("Parent checking child status (non-blocking)...\n");
        
        while (1) {
            pid_t result = waitpid(pid, &status, WNOHANG);  // 非阻塞等待

            if (result == 0) {
                // 子进程还没有结束
                printf("Child process is still running...\n");
                sleep(1);  // 父进程继续执行其他任务
            } else if (result == -1) {
                perror("waitpid failed");
                exit(1);
            } else {
                // 子进程结束
                printf("Child exited with status: %d\n", WEXITSTATUS(status));
                break;
            }
        }
    }

    return 0;
}

输出结果

  • 父进程每隔 1 秒检查一次子进程状态,而不会阻塞自己等待子进程结束。当子进程结束时,父进程获取子进程的退出状态并结束循环。

3.结语

Linux 系统中的进程终止和进程等待是进程管理中的核心内容。通过合理地终止进程并及时进行等待操作,父进程可以有效地处理子进程的退出,避免产生僵尸进程,同时保证系统资源的高效利用。希望通过本文的讲解,读者能够对进程终止和进程等待有更深入的理解。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

萤石联名朱炳仁・铜推出“萤石・国礼大师”AI智能锁 共襄美好家生活

引言&#xff1a;当前&#xff0c;文化与科技正以前所未有的紧密程度相互融合&#xff0c;以人工智能为代表的智能科技的强势介入正推动非遗文化实现从创意策划、生产制造、传播方式乃至保存模式的全面革新&#xff0c;孕育着无限可能。 另一方面&#xff0c;当下智能锁行业竞…

传知代码-字里行间的背叛:博文出卖了你

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 你的博文透露了你内心的秘密 随着社交媒体和短视频行业的快速发展&#xff0c;来自文本、视频和音频的多模态数据爆发式增长。 同时&#xff0c;捕捉设备的广泛使用&#xff0c;加上其使用的简便性、移动能力和低…

界面控件DevExtreme中文教程 - 如何与Amazon S3和Azure Blob存储集成?

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序。从Angular和Reac&#xff0c…

1. 安装框架

一、安装 Laravel 11 框架 按照官方文档直接下一步安装即可 1. 安装步骤 2. 执行数据库迁移 在.env文件中提前配置好数据库连接信息 php artisan migrate二、安装 Filament3.2 参考 中文文档 进行安装 1. 安装 拓展包 composer require filament/filament:"^3.2" -W…

【功能安全】相关项定义item definition

目录 01 item definition定义 02 相关项组成 03 相关项最佳实践 📖 推荐阅读 01 item definition定义 概念阶段的开发是以相关项定义(Item Definition)开始的,相关项定义是对系统的描述,此系统也是标准中安全要求应用的对象。 相关项定义目的: a) 在整车层面对相关…

C++ string(2)

文章目录 1.初识迭代器和范围for1.1迭代器1.2范围for1.3 aout关键字 2.字符串长度相关计算1.size 和 length2. capacity 和 reserve 3.例题演示1. [917. 仅仅反转字母 - 力扣&#xff08;LeetCode&#xff09;](https://leetcode.cn/problems/reverse-only-letters/description…

spring揭秘31-spring任务调度01-spring集成Quartz及JDKTimer定时器

文章目录 【README】【1】Quartz任务调度框架【1.1】Job调度任务【1.2】任务调度触发器Trigger【1.3】\*Quartz框架执行调度任务代码实践【1.3.1】硬编码执行Quartz调度任务【1.3.2】基于生产者模式执行quartz调度任务&#xff08;推荐&#xff09; 【2】spring集成Quartz【2.1…

查找与排序-选择排序

选择排序也是基于“比较”和“交换”两种操作来实现的排序方法 。 每一趟排序在待排序序列中选择关键字最小&#xff08;或最大&#xff09;的数据元素加入到排好序的序列前&#xff08;或后&#xff09;&#xff0c;直至所有元素排完为止。 一、简单选择排序 1.简单…

2024产品管理新风向:项目管理软件不懂敏捷开发?

一、产品管理与敏捷开发的紧密关联 产品管理和敏捷开发之间存在着紧密的关联&#xff0c;二者相互促进&#xff0c;共同为企业创造价值。 &#xff08;一&#xff09;敏捷开发为产品管理带来的优势 敏捷开发能够极大地加快产品上市速度。在传统的开发模式下&#xff0c;产品…

SAP 关于在交货单进行定价条件的确定简介

SAP 关于在交货单进行定价条件的确定简介 业务场景前台操作1、创建交货单2、创建交货单3、创建发票系统配置1、定义条件类型2、定义并分配定价过程3、定义交货的定价过程确定4、维护开票凭证的复制控制SAP交货单定价是针对销售交货单的价格计算过程,通常包括基本价格、折扣、附…

Java读取PDF后做知识库问答_SpringAI实现

​​​​​​​​​​​​​​ 核心思路&#xff1a; 简单来说&#xff0c;就是把PDF文件读取并向量化&#xff0c;然后放到向量存储里面&#xff0c;再通过大模型&#xff0c;来实现问答。 RAG&#xff08;检索增强生成&#xff09;介绍&#xff1a; 检索增强生成&#x…

数据结构——树、二叉树和森林间的转换

前言 介绍 &#x1f343;数据结构专区&#xff1a;数据结构 参考 该部分知识参考于《数据结构&#xff08;C语言版 第2版&#xff09;》129~130页 &#x1f308;每一个清晨&#xff0c;都是世界对你说的最温柔的早安&#xff1a;ૢ(≧▽≦)و✨ 目录 前言 1、基础知识 2…

Qml-Button的使用

Qml-Button的使用 Button属性 Button的继承关系&#xff1a; Button – AbstractButton – Control – Item; Button的属性主要继承于AbstractButton。AbstractButton属性主要如下&#xff1a; a.action:是一个Action类型属性&#xff0c;与QAction类似&#xff0c;用于提供快…

【论文解读系列】EdgeNAT: 高效边缘检测的 Transformer

代码&#xff1a; https://github.com/jhjie/edgenat 论文&#xff1a; https://arxiv.org/abs/2408.10527v1 论文 EdgeNAT: Transformer for Efficient Edge Detection 介绍了一种名为EdgeNAT的基于Transformer的边缘检测方法。 1. 背景与动机 EdgeNAT预测结果示例。(a, b)…

c语言基础程序——经典100道实例。

c语言基础程序——经典100道实例 001&#xff0c; 组无重复数字的数002&#xff0c;企业发放的奖金根据利润提成003&#xff0c;完全平方数004&#xff0c;判断当天是这一年的第几天005&#xff0c;三个数由小到大输出006&#xff0c;输出字母C图案007&#xff0c;特殊图案008&…

【Petri网导论学习笔记】Petri网导论入门学习(七) —— 1.5 并发与冲突

导航 1.5 并发与冲突1.5.1 并发定义 1.14定义 1.15 1.5.2 冲突定义 1.17 1.5.3 一般Petri网系统中的并发与冲突定义 1.18一般网系统中无冲撞概念阻塞&#xff08;有容量函数K的P/T系统&#xff0c;类似于冲撞&#xff09;一般Petri网中并发与冲突共存情况 1.5 并发与冲突 Petr…

lstm基础知识

lstm前言 LSTM(Long short-term memory)通过刻意的设计来避免长期依赖问题&#xff0c;是一种特殊的RNN。长时间记住信息实际上是 LSTM 的默认行为&#xff0c;而不是需要努力学习的东西&#xff01; 在标准的RNN中&#xff0c;这个重复模块具有非常简单的结构&#xff0c;例…

路由器原理和静态路由配置

一、路由器的工作原理 根据路由表转发数据 接收数据包→查看目的地址→与路由表进行匹配找到转发端口→转发到该端口 二、路由表的形成 它是路由器中维护的路由条目的集合&#xff0c;路由器根据路由表做路径选择&#xff0c;里面记录了网段ip地址和对应下一跳接口的接口号。…

【C语言备课课件】(下)指针pointer

目录 定义type *var_name;初始化int *p &a; // p指向变量a的地址 空指针NULL,野指针&#xff0c;指针悬挂 解引用指针的算术运算指针与数组 数组名—首指针二维数组指针 行指针列指针 多级指针&#xff08;进阶&#xff09;数组指针,指针数组&#xff08;进阶&#xff09…

如何利用 Python抓取网页数据 其他方式抓取网页数据列举

在 Python 中可以使用多种方法抓取网页数据&#xff0c;以下是一种常见的方法&#xff0c;使用requests和BeautifulSoup库。 一、安装所需库 在命令提示符或终端中执行以下命令安装requests和BeautifulSoup库&#xff1a; pip install requests pip install beautifulsoup4二…