Linux 进程终止和进程等待

news2024/10/18 13:47:43

目录

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

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

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

相关文章

拼三角问题

欢迎来到杀马特的主页&#xff1a;羑悻的小杀马特.-CSDN博客 目录 一题目&#xff1a; 二思路&#xff1a; 三解答代码&#xff1a; 一题目&#xff1a; 题目链接&#xff1a; 登录—专业IT笔试面试备考平台_牛客网 二思路&#xff1a; 思路&#xff1a;首先明白能组成三角形…

php的echo和print输出语句⑥

在 PHP 中有两个基本的输出方式&#xff1a; echo 和 print。 echo 和 print 区别: echo : 可以输出一个或多个字符串 print : 只允许输出一个字符串。 提示&#xff1a;echo 输出的速度比 print 快&#xff0c; echo 没有返回值&#xff0c;print有返回值1。 <?php …

【赵渝强老师】Oracle的联机重做日志文件与数据写入过程

在Oracle数据库中&#xff0c;一个数据库可以有多个联机重做日志文件&#xff0c;它记录了数据库的变化。例如&#xff0c;当Oracle数据库产生异常时&#xff0c;导致对数据的改变没有及时写入到数据文件中。这时Oracle数据库就会根据联机重做日志文件中的信息来获得数据库的变…

Submariner 服务更新同步测试

测试服务更新同步问题 在集群1 部署 nginx1服务&#xff0c;导出服务&#xff0c;分配的虚拟 IP 为 100.1.255.253 在其他集群检测 serviceimport &#xff0c;可以检测到 nginx1 服务对应的 serviceimport 正常情况下的 serviceexport 如果删除 service 或者 删除 serviceexp…

OpenAI Canvas用户反馈:并不如外界传言般“炸裂”,更不是“AGI的终极交互形态” | LeetTalk Daily...

“LeetTalk Daily”&#xff0c;每日科技前沿&#xff0c;由LeetTools AI精心筛选&#xff0c;为您带来最新鲜、最具洞察力的科技新闻。 Canvas作为一个独立的界面&#xff0c;通过与ChatGPT的结合来提升用户的协作能力和创作效率。尽管用户对其独立性与现有工具的整合存在不同…

闯关leetcode——112. Path Sum

大纲 题目地址内容 解题代码地址 题目 地址 https://github.com/f304646673/leetcode/tree/main/112-Path-Sum 内容 Given the root of a binary tree and an integer targetSum, return true if the tree has a root-to-leaf path such that adding up all the values alo…

OpenCV高级图形用户界面(17)设置一个已经创建的滚动条的最小值函数setTrackbarMin()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::setTrackbarMin 这个函数的作用就是设置指定窗口中轨迹条的最小位置。这使得开发者能够在程序运行时动态地调整轨迹条的范围&#xff0c;而不…

基于STM32的风速风向传感器设计

引言 本项目设计了一个基于STM32的风速和风向传感器系统&#xff0c;能够通过组合使用旋转式风速传感器和电子罗盘&#xff0c;实时测量风速和风向&#xff0c;并将数据通过显示屏或无线模块发送给用户。该系统适用于气象监测、环境监控、农业自动化等场景&#xff0c;具有准确…

微信好友变顾客,7天成效的秘诀

在如今的社交媒体时代&#xff0c; 微信不仅是沟通工具&#xff0c;更是商业营销的重要平台。很多人拥有大量的微信好友&#xff0c;但成交的客户很少&#xff1f;以下四个有效的社交销售秘诀&#xff0c;帮助你在7天内实现转化。 01保持耐心&#xff0c;合理安排跟进时间 在销…

Springboot 整合 Java DL4J 实现安防监控系统

&#x1f9d1; 博主简介&#xff1a;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编程&#xff0c;…

【网络安全】-vulnhub靶场-noob

1.靶机下载&#xff1a; https://www.vulnhub.com/entry/noob-1,746/ 得到ova文件导入虚拟机&#xff0c;并打开虚拟机设置&#xff0c;将靶机-Noob与攻击机-kali的网络适配器都改成NAT仅主机模式&#xff0c;确保两台虚拟机在同一网段上。 2.靶机-Noob ip 判断 命令&#x…

[Vue3核心语法] ref、reactive响应式数据

定义: ref用来定义&#xff1a;基本类型数据、对象类型数据&#xff1b; reactive用来定义&#xff1a;对象类型数据。 使用原则: 若需要一个基本类型的响应式数据&#xff0c;必须使用ref。 若需要一个响应式对象&#xff0c;层级不深&#xff0c;ref、reactive都可以。 …

TCP的建立与终止——三次握手、四次挥手

目录 1. UDP和TCP的区别 2. TCP概述 3. TCP连接的建立&#xff08;三次握手&#xff09; 3.1 为什么TCP客户端最后还要发送一次确认&#xff1f; 3.2 什么是半连接队列&#xff1f; 3.3 半连接队列被填满或遇到SYN洪泛攻击是如何处理&#xff1f; 3.4 三次握手过程中可以…

JavaWeb合集03-Maven

三、Maven Maven是apache旗下的一一个开源项目&#xff0c;是一款用于管理和构建java项目的工具。 作用: 依赖管理&#xff1a;方便快捷的管理项目依赖的资源(jar包)&#xff0c; 避免版本冲突问题。统一项目结构&#xff1a;提供标准、统一的项目结构&#xff0c;maven项目。…

map和set的模拟实现

一.内容介绍 1.set采用Key的搜索场景&#xff0c;map采用Key/Value的搜索场景&#xff0c;二者的底层均可以用红黑树实现&#xff0c;为了降低代码的冗余量可以通过对红黑树模板的参数做少许改动达到一棵红黑树的基层实现set和map两个派生类的目的。 一些问题&#xff1a; 1…

uniapp uni.uploadFile errMsg: “uploadFile:fail

uniapp 上传后一直显示加载中 1.检查前后端上传有无问题 2.检查失败信息 await uni.uploadFile({url,filePath,name,formData,header,timeout: 30000000, // 自定义上传超时时间fail: async function(err) {$util.hideAll()// 失败// err 返回 {errMsg: "uploadFile:fai…

【达梦数据库】组态王连接达梦数据库的操作步骤

目录 背景环境版本1、建立ODBC连接配置三级目录 背景 客户咨询组态王如何连接达梦数据库&#xff0c;在查找资料时发现目前网络上没有资料适配达梦数据库 环境版本 Window版本&#xff1a;win11 组态王软件&#xff1a;32位 达梦数据库&#xff1a;32位 1、建立ODBC连接配置…

创客项目秀|基于xiaoESP32C3的桌面嵌入式充电站

今天小编给大家带来的是来自B站的新人UP主“不做点东西就焦虑”的桌面充电站项目&#xff0c;该充电站支持有线和无线两种充电方式&#xff0c;为了尽可能多的为桌面的USB设备统一供电&#xff0c;有线充电接口达到13路&#xff0c;充电站的外观试用铝合金CNC加工&#xff0c;具…

HarmonyOS 开发知识总结

1. HarmonyOS 开发知识总结 1.1. resources->base->media中不可以新建文件夹&#xff1f; 项目图片路径resources->base->media中不可以新建文件夹&#xff0c;图片全平级放里面&#xff0c;查找图片不方便&#xff0c;有没有什么其他的办法解决这个难点&#xff…

软件测试学习笔记丨Pycharm运行与调试

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/23454 Pycharm作为集成开发环境&#xff0c;除了可以编写脚本&#xff0c;还可以运行和调试自己的代码&#xff0c;下面就为大家介绍一下pycharm运行和调试代码的功能如何使用。 代码运行 编…