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

Linux系统——进程结束时退出的分析与总结(关于wait与waitpid函数)

下面是一个面向新手、由浅入深的Linux 进程退出机制详解,我们将用最通俗的语言来讲清楚:一个进程到底是怎么“结束”的?结束后又发生了什么?我们程序员要注意什么?

一、什么是进程退出?

进程退出(也叫进程终止)是指一个运行中的程序结束了它的生命周期——不管是正常执行完了,还是出错提前结束,或者被别的程序(比如你按了 Ctrl+C)强制结束。

简单理解
就像一个人下班离开公司,要不要打卡?有没有留下笔记?是不是留下了饭盒?这些细节就对应着进程退出时的行为。


二、进程退出的方法有哪些?

我们可以从三种层次来看“进程是怎么退出的”:


1. 主动退出(自己走人)

1.1 return 退出(从 main() 函数返回)

这是最常见、最自然的退出方式:

int main() {printf("程序执行完毕!\n");return 0;
}
  • 退出码:返回的 0 会作为进程的退出状态。

  • 特点:适合程序自然结束时使用。

1.2 调用 exit() 函数退出

#include <stdlib.h>int main() {printf("出错了,退出程序!\n");exit(1);  // 退出码为 1,通常表示出错
}
  • exit(int status):告诉操作系统我结束了,并交个“退出码”。

  • 它会自动做一些清理工作,如关闭文件、刷新缓冲区。

1.3 returnexit() 的区别?

特点returnexit()
执行位置只在 main() 中用程序的任意位置都能用
退出码作为 main 的返回值参数直接作为退出码
清理行为等价于 exit()会执行清理函数,如 atexit() 注册的函数

2. 立即终止(突然走人)

2.1 _exit()_Exit():直接终止,啥也不管

#include <unistd.h>int main() {_exit(2);  // 立即终止,不做清理
}
  • 不刷新缓冲区、不释放内存、不执行 atexit() 注册的函数。

  • 用于特殊场景,如 vfork() 中子进程不能影响父进程的资源。

2.2 被信号杀死(比如你按了 Ctrl+C)

$ ./my_program
^C   ← 用户按 Ctrl+C,系统发送 SIGINT 信号
  • 这是被外部打断的方式,操作系统或其他程序(甚至用户)可以向进程发送信号,让它中止。

  • 常见信号:

    • SIGINT(Ctrl+C)

    • SIGTERM(系统默认终止)

    • SIGKILL(强制终止,无法捕获)


3. 出错导致的异常退出

比如:

int *p = NULL;
*p = 10;  // 段错误(Segmentation fault)
  • 程序会被系统强制结束,通常返回值为 139(= 128 + SIGSEGV 信号值)。

  • 这类错误退出通常表示程序有 bug,需要调试工具(如 gdb)分析。


三、进程退出之后,系统会做什么?

  1. 释放资源:包括内存、打开的文件、网络连接等。

  2. 记录退出状态:进程退出时会带一个“退出码”(status),父进程可以通过 wait()waitpid() 获取它。

  3. 变成僵尸进程:如果父进程没来“领尸”(调用 wait()),子进程的状态信息会暂时留在系统中,成为僵尸进程。


什么是僵尸进程?

当子进程退出了,但父进程没来处理它的退出状态,它就会“挂”在系统里,状态为 Z(Zombie)

  • 不会占用内存,但会占用进程号(PID);

  • 如果大量僵尸进程存在,系统可能无法创建新进程。

解决方法:

父进程必须调用 wait()waitpid() 来“回收”子进程。


四、进程退出的总结图(简单版)

+--------------------+
| 进程开始运行       |
+--------------------+|v
+--------------------+
| return / exit()    |←—— 正常退出
+--------------------+|v
+--------------------+
| 系统清理资源       |
| 回收文件/内存等    |
+--------------------+|v
+--------------------+
| 父进程通过 wait()  |
| 获取退出状态       |
+--------------------+

五、退出码

1、什么是退出码(Exit Code)?

当一个进程结束时,它会向操作系统“上交”一个退出状态值(退出码),告诉系统自己是“正常完成了”,还是“出错了”,以及出错的原因。

退出码的范围:

  • 是一个 0~255 的整数值(8 位),只能传一个字节。

  • 通常约定:

    • 0:表示正常退出(Success)

    • 0:表示有错误(Error)

如何设置退出码?

你可以通过 returnexit() 设置退出码:

int main() {return 0;    // 正常退出// 或者// exit(1);  // 表示有错误退出
}

如何查看退出码?

在 Linux 终端中运行程序后,可以用 echo $? 查看刚才程序的退出码:

$ ./my_program
$ echo $?
0

六、什么是 wait() 函数?

当一个进程(父进程)通过 fork() 创建了子进程后,它通常要等待子进程结束,并获取子进程的退出码。这个功能就是 wait() 函数完成的。

函数原型:

#include <sys/wait.h>
pid_t wait(int *status);

参数:

  • status:这是一个“输出参数”,用于接收子进程的退出状态。

返回值:

  • 成功:返回已结束的子进程的 PID。

  • 失败:返回 -1,可以用 perror() 查看错误原因。


示例:父进程使用 wait() 获取退出码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork failed");return 1;}if (pid == 0) {// 子进程printf("Child process running, PID=%d\n", getpid());exit(42);  // 子进程返回退出码 42} else {// 父进程int status;wait(&status);  // 等待子进程结束if (WIFEXITED(status)) {// WIFEXITED:判断子进程是否正常退出int code = WEXITSTATUS(status);  // 提取退出码printf("Child exited normally with code %d\n", code);} else {printf("Child exited abnormally\n");}}return 0;
}

输出示例:

Child process running, PID=1234
Child exited normally with code 42

辅助宏函数(解释 wait() 结果用的)

Linux 提供了一些宏,用来解释 wait() 返回的状态值:

宏名作用说明
WIFEXITED(status)子进程是否正常通过 exit()return 退出
WEXITSTATUS(status)提取子进程退出码(如果是正常退出)
WIFSIGNALED(status)子进程是否因信号中止(如 SIGSEGV、SIGINT)
WTERMSIG(status)如果是信号中止的,提取信号编号
WIFSTOPPED(status)子进程是否暂停(如使用调试器)

拓展:waitpid() 函数

waitpid() 是 Linux 系统编程中用于等待子进程退出的系统调用,功能比 wait() 更灵活、更强大。与 wait() 只能“等任意一个子进程”不同,waitpid() 可以等待特定的子进程,还可以设置等待行为(阻塞或非阻塞)。

原型:

pid_t waitpid(pid_t pid, int *status, int options);

1. pid 参数 —— 选择你要等的“谁”

  • > 0:等待指定 PID 的子进程退出。

  • = 0:等待和当前进程在 同一进程组 中的任意子进程。

  • < -1:等待属于 进程组 ID = |pid| 的任何子进程。

  • = -1默认行为,等任意一个子进程(与 wait() 等价)。

2. status 参数 —— 子进程留下的“遗言”

这是一个整型指针,用来返回子进程的退出信息。我们可以通过宏函数来解析它。

3. options 参数 —— 控制等待行为

常用选项:

选项含义
0默认阻塞,直到子进程退出
WNOHANG非阻塞,如果没有子进程退出就立刻返回
WUNTRACED当子进程因信号停止时也返回
WCONTINUED子进程被 SIGCONT 恢复时也返回(用于作业控制)

示例:

waitpid(child_pid, &status, 0);  // 等待指定 PID 的子进程
返回值意义
> 0返回已退出子进程的 PID
0options = WNOHANG 且没有退出的子进程时返回
-1出错(如没有子进程、参数无效),可用 perror() 查看原因

wait()waitpid() 的区别

特点wait()waitpid()
等待对象任意一个子进程指定某个子进程或任意、进程组等
灵活度
支持非阻塞支持(通过 WNOHANG
推荐场景简单的单子进程处理多子进程、并发、异步或精细控制场景

总结一句话

  • 退出码是子进程留给父进程的一句“临别遗言”;

  • wait() / waitpid() 是父进程“回收子进程尸体”和“查看遗言”的工具;

  • 不调用 wait() 会产生僵尸进程,所以系统编程中必须管理好子进程的退出。


常见误区提示

误区正确做法
只用 exit() 而不让父进程调用 wait()会产生僵尸进程,应使用 wait()
status 直接当作退出码使用应该用 WEXITSTATUS(status) 提取
忽略 WIFEXITED 检查有可能子进程是被信号终止的,应判断

七、开发者常犯的错误和建议

错误行为建议
忘记处理子进程(导致僵尸)父进程要记得调用 wait() 或使用信号处理回收
子进程用 exit() 而不是 _exit()(尤其在 vfork 中)_exit() 避免影响父进程
退出前不清理资源使用 atexit() 注册清理函数,或手动关闭文件

八、总结一句话

进程退出是一个有秩序、有信息、有影响的过程。程序员不仅要学会退出,更要学会“优雅地退出”。

记住这些:

  • 正常退出用 returnexit()

  • 特殊退出用 _exit()

  • 子进程退出要 wait()

  • 退出码有含义要好好利用;

相关文章:

  • 扣子(Coze)案例:工作流生成小红书心理学卡片
  • 测序的原理
  • 鸿蒙OSUniApp 实现的地图定位与导航功能#三方框架 #Uniapp
  • 5月15日day26打卡
  • Spring Boot 拦截器:解锁5大实用场景
  • 移动端网络调试全流程:从常见抓包工具到Sniffmaster 的实战体验
  • 小刚说C语言刷题—1088求两个数M和N的最大公约数
  • 每周靶点:TIGIT、ICAM1及文献分享
  • 嵌入式自学第二十二天(5.15)
  • 21、工业大数据分析与实时告警 (模拟根因分析) - /数据与物联网组件/bigdata-root-cause-analysis
  • 线程的两种实现方式
  • 鸿蒙OSUniApp实现的倒计时功能与倒计时组件(鸿蒙系统适配版)#三方框架 #Uniapp
  • 低损耗高效能100G O Band DWDM 10km光模块 | 支持密集波分复用
  • Elasticsearch 快速入门指南
  • ChromaDB 向量库优化技巧实战
  • SymPy | 使用SymPy求解多元非线性方程组
  • 合并两个有序数组的高效算法详解
  • 1.1 认识编程与C++
  • 黑马k8s(七)
  • 腾讯开源实时语音大模型VITA-audio,92mstoken极速响应,支持多语言~
  • 端午小长假前夜火车票今日开抢,多个技巧提高购票成功率
  • 证监会:2024年依法从严查办证券期货违法案件739件,作出处罚决定592件、同比增10%
  • 基金经理调仓引发大金融板块拉升?公募新规落地究竟利好哪些板块
  • 哪种“网红减肥法”比较靠谱?医学专家和运动专家共同解答
  • 上海市重大工程一季度开局良好,崇明线等按既定计划加快建设
  • 阿坝州委书记徐芝文已任四川省政府党组成员