当前位置: 首页 > news >正文

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

相关文章:

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 非阻…...

如何查看默认网关地址:详细步骤

在日常的网络配置与故障排查中&#xff0c;了解并正确查看默认网关地址是一项基础且至关重要的技能。默认网关是连接本地网络与外部网络&#xff08;如互联网&#xff09;的关键节点&#xff0c;它扮演着数据包转发的重要角色。无论是家庭网络、办公室网络还是更复杂的网络环境…...

什么是方法的返回值?方法有哪几种类型?静态方法为什么不能调用非静态成员?静态方法和实例方法有何不同?

什么是方法的返回值?方法有哪几种类型&#xff1f; 方法的返回值 是指我们获取到的某个方法体中的代码执行后产生的结果&#xff01;&#xff08;前提是该方法可能产生结果&#xff09;。返回值的作用是接收出结果&#xff0c;使得它可以用于其他的操作&#xff01; 我们可以…...

Qt开发——Qt项目打包、整合以及生成安装包保姆级教程(Windows系统)

目录 Windows下打包Qt项目 1.Qt系统环境变量的配置 2.打包 3.打包整合为一个.exe文件 4.生成安装包 做完了一个Qt项目之后&#xff0c;要干嘛呢&#xff0c;很显然要打包给别人&#xff0c;让别人也能使用这个软件&#xff0c;本期我们就来学习Qt打包&#xff0c;本期内容分…...

大数据-180 Elasticsearch - 原理剖析 索引写入与近实时搜索

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…...

大数据-172 Elasticsearch 索引操作 与 IK 分词器 自定义停用词 Nginx 服务

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…...

【Java后端】之 ThreadLocal 详解

想象一下&#xff0c;你有一个工具箱&#xff0c;里面放着各种工具。在多人共用这个工具箱的时候&#xff0c;很容易出现混乱&#xff0c;比如有人拿走了你的锤子&#xff0c;或者你找不到合适的螺丝刀。为了避免这种情况&#xff0c;最好的办法就是每个人都有自己独立的工具箱…...

2.链表(代码随想录——python版本)

2.链表&#xff08;代码随想录——python版本&#xff09; 链表的概念&#xff1a; 链表是由指针串联在一起的线性结构&#xff0c;一个节点&#xff08;node&#xff09;由两部分组成&#xff1a; 数据域——用来存储数据&#xff1b;指针域——用来指向下一个节点&#xf…...

6个解决“由于找不到vcruntime140_1.dll无法继续执行代码”问题的方法

vcruntime140_1.dll丢失的问题在Windows操作系统中相对常见&#xff0c;它通常与Microsoft Visual C Redistributable有关。本文将详细解读vcruntime140_1.dll丢失的原因、解决方法以及预防措施&#xff0c;帮助用户更好地应对这一问题。 一&#xff0c;vcruntime140_1.dll文件…...

常用数据库获取表,视图,列,索引信息

一、分页获取数据库用户的所有表 (1)、Oracle&#xff0c;OceanBase(Oracle内核版)&#xff0c;DM 使用ALL_TABLES&#xff0c;需要添加当前用户作为查询条件 select a3.* from (select a2.* from (select a1.*, rownum rn1 from ( select t1.table_name, t2.comments fro…...

架构设计笔记-16-嵌入式系统架构设计理论与实践

目录 知识要点 嵌入式微处理器 存储器&#xff08;memory&#xff09; 内&#xff08;外&#xff09;总线逻辑 嵌入式操作系统&#xff08;Embedded Operating System&#xff0c;EOS&#xff09; 通用中间件 嵌入式中间件的一般架构 典型嵌入式中间件系统 案例分析 1…...

SpringSecurity使用介绍

1、SpringSecurity 1.1 SpringSecurity简介 Spring Security是基于Spring的安全框架,提供了包含认证和授权的落地方案&#xff1b;Spring Security底层充分利用了Spring IOC和AOP功能&#xff0c;为企业应用系统提供了声明式安全访问控制解决方案&#xff1b;SpringSecurity可…...

# Js 回调函数

Js 回调函数 文章目录 Js 回调函数回调函数的定义和使用回调函数的常见用途异步操作事件处理 回调函数的优点和缺点优点缺点 回调地狱解决回调地狱的方法使用 Promise使用 async/await 应用函数式编程中的回调函数高阶函数函数柯里化 异步编程中的回调函数回调函数的错误处理传…...

COOLSHELL文章:从Code Review 谈如何做技术【阅读笔记】

从Code Review 谈如何做技术原文链接&#xff1a;https://coolshell.cn/articles/11432.html#google_vignette 工程师需要有责任心和修养&#xff0c;不是做出来就了事&#xff0c;而是要做漂亮。 这也是山寨和工业的区别&#xff0c;只以做出来为标准是劳动密集型的装配生产线…...

3.1.1 ReactOS系统中二叉树创建一个MEMORY_AREA节点

二叉树中创建一个MEMORY_AREA节点&#xff1a; 二叉树中创建一个MEMORY_AREA节点&#xff1a; MmCreateMemoryArea() 参数AddressSpace是MADDRESS SPACE结构指针&#xff0c;所指向的数据结构代表着一个进程的用 户空间。 参数BaseAddress是个指针&#xff0c;用来给定和返回内…...

三、Linux 安装全攻略

Linux 安装全攻略 在当今的科技时代&#xff0c;Linux 操作系统以其稳定性、安全性和高度的可定制性而备受青睐。本文将详细介绍 Linux 的安装过程&#xff0c;包括关键步骤和下载资源获取方式&#xff0c;帮助你顺利踏上 Linux 之旅。 一、为什么选择 Linux Linux 有许多优…...

Ansible自动化工具

一、Ansible概述 1.1 什么是Ansible Ansible 是一个开源的自动化工具&#xff0c;用于配置管理、应用程序部署和任务自动化。它让你可以通过编写简单的 YAML 文件&#xff08;剧本&#xff0c;Playbooks&#xff09;&#xff0c;轻松管理和配置多个服务器。Ansible 的特点是无…...

Flutter Container组件

Over the past few years, I’ve been fortunate to collaborate with interior designers, and there’s a distinct flair to their approach to crafting captivating interiors. It’s not just about arranging furniture randomly; they meticulously plan layouts, sele…...

IPv6 DNS简介

IPv6网络中的每台主机都是由IPv6地址来标识的&#xff0c;用户只有获得待访问主机的IPv6地址&#xff0c;才能够成功实现访问操作。对于用户来讲&#xff0c;记住主机的IPv6地址是相当困难的&#xff0c;因此设计了一种字符串形式的主机命名机制&#xff0c;这就是域名系统。用…...

【Python-AI篇】数据结构和算法

1. 算法概念 1.1 什么是数据结构 存储&#xff0c;组织数据的方式 1.2 什么是算法 实现业务目的的各种方法和思路算法是独立的存在&#xff0c;只是思想&#xff0c;不依附于代码和程序&#xff0c;可以使用不同语言实现&#xff08;java&#xff0c;python&#xff0c;c&a…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...