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

017 进程控制 —— 终止进程

🦄 个人主页: 小米里的大麦-CSDN博客
🎏 所属专栏: Linux_小米里的大麦的博客-CSDN博客
🎁 GitHub主页: 小米里的大麦的 GitHub
⚙️ 操作环境: Visual Studio 2022

在这里插入图片描述

文章目录

    • 进程控制 —— 终止进程
      • 一、进程退出场景
      • 二、进程的退出码
        • 1. 定义
        • 2. 为什么以 `0` 表示代码执行成功,以 `非0` 表示代码执行错误?
        • 3. `errno` 常量和 `strerror` 函数(牵扯信号,初步了解)
      • 三、进程常见退出方法
        • 1. `exit()` 函数
        • 2. `_exit()` 函数
        • 3. `return` 退出
      • 三、关键区别对比
      • 四、代码示例分析
        • 1. 父子进程退出行为差异
        • 2. `atexit()` 注册清理函数
      • 五、进程终止后的状态
      • 小结

进程控制 —— 终止进程

一、进程退出场景

从我们的视角来看进程终止的场景一般就是以下三种:

  1. 代码运行完毕,结果正确(一般不关心)。
  2. 代码运行完毕,结果不正确。
  3. 代码异常终止。

但是进程也可能因多种原因终止,比如:

场景说明
正常完成任务程序执行完所有代码逻辑后退出
异常错误终止遇到不可恢复的错误(如段错误、除零错误)
主动终止调用退出函数(exit()/_exit())或通过 return 退出
被动终止收到终止信号(如 SIGKILLSIGTERM
被父进程杀死父进程调用 kill() 函数发送信号,使子进程退出。

看进程终止的角度、进程终止的原因等不同方面来解释进程的终止,虽然说法上不同,但也大同小异,我们只需要记住一点:

所有进程的退出方式都可以归为两大类:正常退出异常退出,而主动或被动,是从行为发起方角度来分的。进程出现异常,本质是我们的进程收到了对应的信号!!


二、进程的退出码

我们都知道 main 函数是代码的入口,但实际上 main 函数只是用户级别代码的入口,main 函数也是被其他函数调用的,也就是说 main 函数是间接性被操作系统所调用的。

既然 main 函数是间接性被操作系统所调用的,那么当 main 函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为 main 函数的返回值返回,我们一般以 0 表示代码成功执行完毕,以非 0 表示代码执行过程中出现错误,这就是为什么我们都在 main 函数的最后返回 0 的原因。

1. 定义

进程退出码:是进程终止时向 操作系统 返回的一个 整数值,用于标识该进程是否 成功完成任务出现了错误

退出码含义说明
0表示进程 成功 退出(Success)
1~255表示进程 异常错误 退出(Failure)
其它值可以由程序自定义(常用于表示不同类型的错误)

当我们的代码运行起来就变成了进程,当进程结束后 main 函数的返回值实际上就是该进程的进程退出码,我们可以使用 echo $? 命令查看最近一次进程退出的退出码信息。

例如,对于下面这个简单的代码:

#include <stdio.h>
int main()
{printf("Hello, World!\n");return 0;
}

代码运行结束后,我们可以使用 echo $? 查看该进程的进程退出码:

image-20250406133322513

这里进程退出码显示 0 便是可以确定程序顺利执行完毕了。

实际上 Linux 中的 lspwd 等命令都是可执行程序,使用这些命令后我们也可以查看其对应的退出码。

image-20250406133818700

注意: 命令执行错误后,其退出码就是非 0 的数字,该数字具体代表某一错误信息。 退出码都有对应的字符串含义,帮助用户确认执行失败的原因,而这些退出码具体代表什么含义是人为规定的,不同环境下相同的退出码的字符串含义可能不同。

2. 为什么以 0 表示代码执行成功,以 非0 表示代码执行错误?

因为代码执行成功只有一种情况,成功了就是成功了,而代码执行错误却有多种原因,例如内存空间不足、非法访问以及栈溢出等等,我们就可以用这些 非0 的数字分别表示代码执行错误的原因。

3. errno 常量和 strerror 函数(牵扯信号,初步了解)

查看信号对应的退出码

信号终止的进程退出码为 128 + 信号编号。可通过命令 kill -l(列出所有信号及其编号)查看信号列表:

image-20250406153812273

上面我们提到我们可以通过不同的退出码来代表不同的错误信息,那么不同的退出码究竟各自代表什么信息呢?我们可以通过 strerror 函数来查看, 比如我们来看一下退出码 010 所代表的信息:

#include<stdio.h>
#include<string.h>
int main()
{for(int i=0;i<=10;i++){printf("%d: %s\n",i,strerror(i));}return 0;
}                

运行结果:

image-20250406140838340

进程在退出是会有退出码,我们可以通过 echo 来查看退出码,那我们如何获取呢?

C/C++中其实还定义了一个叫 errno 的常量来记录错误码,所以我们就可以将 errno 常量与 strerror 函数结合使用,用 errno 来记录进程的错误码,然后传给 strerror 函数得到错误信息,比如下面的例子:

#include<stdio.h> 
#include<unistd.h>                                                                                                                                                                                           
#include<string.h>
#include<stdlib.h>
#include<errno.h>                                       //注意要带好头文件
int main()
{int ret = 0;char* p = (char*)malloc(1000 * 1000 * 1000 * 4);    //这个扩容肯定会出错的,因为扩容空间太大了if (p == NULL){printf("mallo error, %d:%s\n", errno, strerror(errno));   //errno会记录错误码,将它传到strerror中就可以得到错误信息ret = errno;                                    //将错误码作为返回值返回,从而让父进程得到返回信息}else{printf("malloc success\n");}return ret;
}

image-20250406141949307

三、进程常见退出方法

1. exit() 函数
  • 头文件#include <stdlib.h>

  • 行为

    • 执行标准清理操作(刷新缓冲区、关闭文件描述符等)。
    • 调用通过 atexit() 注册的函数。
    • 返回状态码给父进程(通过 wait() 获取)。
  • 示例

    #include <stdlib.h>
    int main()
    {exit(3);  // 设置退出码为 3
    }
    
2. _exit() 函数
  • 头文件#include <unistd.h>(函数:void _exit(int status);

  • 行为

    • status 定义了进程的终止状态,父进程通过 wait 来获取该值,虽然 statusint,但是仅有低 8 位可以被父进程所用。所以 exit(-1) 时,在终端执行 echo $? 发现返回值是 255
    • 立即终止 进程(系统调用级别的退出),不执行任何清理(缓冲区不刷新、atexit() 函数不调用)。
    • 适用于子进程在 fork() 后需要快速退出的场景。
  • 示例

    int main()
    {printf("Hello, World!\n");_exit(0);			// 立刻退出,状态码为0printf("这一行将不会被打印。.\n");
    }
    
3. return 退出
  • 行为

    • main() 函数中,return 等效于调用 exit()
    • 在其他函数中,return 仅退出当前函数。
  • 示例

    int main()
    {return 42;  // 等效于 exit(42)
    }
    

[!WARNING]

警告:下面的程序会源源不断的创建僵尸进程,直至将系统资源耗尽!请谨慎使用!实测:在虚拟机中运行 20 秒不到,系统直接卡死。

#include <unistd.h>
#include <sys/types.h>int main()
{while (1){if (fork() == 0){_exit(0); // 子进程立即退出,成为僵尸进程}}return 0;
}

三、关键区别对比

image-20250406135753463

方法是否刷新缓冲区是否调用 atexit()适用场景
exit()✅ 是✅ 是正常退出,需清理资源
_exit()❌ 否❌ 否子进程快速退出或错误紧急终止
return✅ 是(仅 main✅ 是(仅 mainmain() 函数中的简洁退出方式

四、代码示例分析

1. 父子进程退出行为差异
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{printf("Start (PID:%d)\n", getpid());   // 注意:无换行,缓冲区未刷新if (fork() == 0)                        // 子进程{printf("Child exiting\n");exit(0);                            // 刷新缓冲区并退出}else                                    // 父进程{sleep(1);printf("Parent exiting\n");_exit(0);                           // 不刷新缓冲区}
}

输出结果

# 由于 printf 未刷新缓冲区,子进程继承了未刷新的缓冲区内容,导致重复输出:
Start (PID:123)
Child exiting
Start (PID:123)  // 父进程的缓冲区未刷新,被子进程继承后输出
Parent exiting
2. atexit() 注册清理函数
#include <stdlib.h>
#include <stdio.h>void cleanup()
{printf("Cleanup done!\n");
}int main()
{atexit(cleanup);  // 注册清理函数printf("Main running\n");exit(0);          // 会调用 cleanup()
}

输出

Main running
Cleanup done!

五、进程终止后的状态

  1. 僵尸进程(Zombie)

    • 进程已终止,但父进程未通过 wait() 回收其资源。
    • 解决方案:
      • 父进程调用 wait()waitpid()
      • 忽略 SIGCHLD 信号:signal(SIGCHLD, SIG_IGN)注意:在某些系统中,忽略 SIGCHLD 会自动回收子进程,但并非所有系统都支持这一行为!
  2. 孤儿进程

    • 父进程先退出,子进程被 init(PID = 1)接管。
    • 无害,init 会自动回收孤儿进程。

小结

  • exit()(优先使用 ):安全退出,适合大多数场景,确保资源正确释放。
  • _exit():紧急退出,跳过清理。子进程慎用,除非明确需要跳过清理。
  • return:仅在 main() 中等效于 exit()
  • 进程管理:正确处理父子进程关系,避免资源泄漏。
http://www.xdnf.cn/news/1119709.html

相关文章:

  • C语言-流程控制
  • 深入浅出Kafka Consumer源码解析:设计哲学与实现艺术
  • gitlab-ci.yml
  • Spark 和 Hadoop MapReduce 的基本概念及区别
  • 代码随想录算法训练营第四十九天|单调栈part2
  • PHP password_verify() 函数
  • vue vxe-tree 树组件加载大量节点数据,虚拟滚动的用法
  • 【AutoCAD全网最新版】AutoCAD 2026 保姆级下载安装注册使用详细图文教程
  • 借助DeepSeek编写输出漂亮表格的chdb客户端
  • [源力觉醒 创作者计划]_文心大模型4.5开源部署指南:从技术架构到实战落地
  • sfe_py的应力云图计算与显示step by step
  • 【LeetCode240.搜索二维矩阵Ⅱ】以及变式
  • iOS高级开发工程师面试——RunLoop
  • C++类模版与友元
  • 大数据领域开山鼻祖组件Hadoop核心架构设计
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | GithubProfies(GitHub 个人资料)
  • 编译器 VS 解释器
  • 电脑升级Experience
  • Linux操作系统之信号:信号的产生
  • 【C++进阶】---- 多态
  • 鹧鸪云:别墅光储项目方案设计的最终选择
  • 【Linux系统】进程切换 | 进程调度——O(1)调度队列
  • Linux:3_基础开发⼯具
  • 【Linux】基本指令详解(一) 树状文件结构、家目录、绝对/相对路径、linux文件类型
  • 使用systemctl命令控制软件的启动和关闭
  • 打破空间边界!Nas-Cab用模块化设计重构个人存储逻辑
  • 各种开发语言主要语法对比
  • Codeforces Round 1019 (Div. 2) A-D
  • GPU网络运维
  • UV vs Pip:Python 包管理的革命性进化