news 2026/2/6 11:14:49

Linux进程终止

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux进程终止

一、进程的 8 种终止方式:正常与异常的边界

进程终止分为正常终止异常终止两大类,共 8 种常见方式。二者的核心区别在于:正常终止是进程主动完成任务后退出,退出状态由用户指定;异常终止是进程因外部信号或内部错误被迫退出,退出状态由内核决定。

1.1 正常终止:主动退出,可控清理

正常终止包含 4 种方式,均会按照预设逻辑完成资源释放,部分方式还会执行完整的清理流程。

  1. main 函数中的 return 语句这是最常见的进程终止方式,但需注意:return仅在main函数中触发进程终止,在普通函数中仅表示函数返回。main函数中return n等价于exit(n),其中n为用户指定的退出状态码。
  2. exit () 函数:C 标准库的全量清理方案函数原型为void exit(int status);,属于 C 标准库函数,其核心特性是退出前执行完整的清理流程
    • 刷新所有标准 IO 缓冲区(例如printf未用fflush刷新的内容会被输出);
    • 执行通过atexit()on_exit()注册的自定义清理函数;
    • 关闭所有打开的文件描述符;
    • 最终调用_exit()系统调用完成进程终止。状态码约定:EXIT_SUCCESS(值为 0)表示执行成功,EXIT_FAILURE(值为 1)表示执行失败。
  3. _exit ()/_Exit () 函数:系统调用的极速终止函数原型为void _exit(int status);,属于 Linux 系统调用,其核心特性是无任何额外清理操作
    • 不刷新 IO 缓冲区,不执行atexit()注册的清理函数;
    • 仅关闭打开的文件描述符,直接释放进程资源并终止。适用场景:需要快速终止进程,且无需保留缓冲区数据的场景。
  4. 主线程退出或 pthread_exit 调用针对多线程进程,终止逻辑与线程状态强相关:
    • 若主线程退出,且进程中无其他非分离线程运行,则进程终止;
    • 若主线程调用pthread_exit(),仅主线程终止,其他线程可继续执行,直到所有线程结束,进程才会终止。

1.2 异常终止:被动退出,内核主导

异常终止同样包含 4 种方式,进程无法自主控制退出时机,退出状态由内核根据终止原因分配。

  1. abort () 函数:强制生成核心转储函数原型为void abort(void);,其功能是向进程发送SIGABRT信号。该信号无法被捕获或忽略,会强制终止进程,并生成核心转储文件(core dump),用于调试程序崩溃的根本原因。
  2. kill 命令或 kill () 系统调用:外部信号触发终止这是通过外部信号终止进程的常用方式,分为两种操作形式:
    • 命令行层面:使用kill -信号名 PID命令,例如kill -9 1234发送SIGKILL信号强制终止 PID 为 1234 的进程;
    • 程序层面:调用kill()系统调用,例如kill(pid, SIGKILL)向指定 PID 的进程发送终止信号。常见终止信号:SIGKILL(9 号信号)强制终止,无法拦截;SIGTERM(15 号信号)请求终止,是kill命令的默认信号。
  3. 最后一个线程被 pthread_cancel 取消多线程进程中,若最后一个存活的线程被pthread_cancel()函数取消,则进程会触发异常终止,终止逻辑由内核的信号机制主导。
  4. 触发致命错误:内核发送终止信号进程运行时触发内部错误,内核会自动发送对应信号终止进程,常见场景包括:
    • 访问非法内存地址(触发SIGSEGV信号);
    • 执行除以 0 的运算;
    • 总线错误(触发SIGBUS信号)等。

二、exit ()、_exit () 与 return:执行流程的核心差异

正常终止的三种核心方式(main函数returnexit()_exit()),其底层执行逻辑存在明显差异,关键在于是否执行清理操作、是否刷新缓冲区。三者的完整执行流程对比如下:

终止方式完整执行流程核心差异点
main 函数中return nreturn n→ 调用exit(n)→ 刷新 IO 缓冲区 → 执行atexit清理函数 → 关闭文件描述符 → 调用_exit()→ 终止进程依赖exit()完成全量清理,仅在main函数中生效
exit(n)exit(n)→ 刷新 IO 缓冲区 → 执行atexit清理函数 → 关闭文件描述符 → 调用_exit()→ 终止进程主动触发全量清理,可在程序任意位置调用
_exit(n)_exit(n)→ 关闭文件描述符 → 直接终止进程无任何额外清理操作,进程极速终止

验证示例:缓冲区刷新差异

我们可以通过一个简单的代码示例,直观感受三者的缓冲区处理差异:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> // 测试 exit():会刷新缓冲区 void test_exit() { printf("测试 exit() 缓冲区:"); // 无换行符,默认不刷新缓冲区 exit(EXIT_SUCCESS); // 执行 exit 会触发缓冲区刷新 } // 测试 _exit():不刷新缓冲区 void test__exit() { printf("测试 _exit() 缓冲区:"); // 无换行符,缓冲区未刷新 _exit(EXIT_SUCCESS); // 直接终止,不处理缓冲区数据 } int main() { // 取消注释分别测试 // test_exit(); // test__exit(); return 0; }

运行结果:

  • 执行test_exit():输出测试 exit() 缓冲区:
  • 执行test__exit():无任何输出。

三、进程退出状态的传递与解析

进程终止时,会将退出状态信息传递给父进程,这些信息包括终止类型(正常 / 异常)、状态码 / 终止信号编号等。父进程需要通过wait()waitpid()函数获取这些信息,同时完成子进程资源的回收。

3.1 核心解析宏:提取退出状态信息

父进程通过wait()/waitpid()获取的status参数是一个整数,内核通过该整数封装了子进程的终止详情。Linux 提供了 4 个核心宏来解析该值:

功能说明
WIFEXITED(status)判断子进程是否正常终止(return/exit ()/_exit ()),返回非 0 表示是,0 表示否
WEXITSTATUS(status)WIFEXITED(status)为真时有效,获取正常终止的状态码(如exit(8)中的 8)
WIFSIGNALED(status)判断子进程是否被信号异常终止,返回非 0 表示是,0 表示否
WTERMSIG(status)WIFSIGNALED(status)为真时有效,获取终止子进程的信号编号(如 9 表示SIGKILL

3.2 代码示例:解析子进程退出状态

#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> #include <signal.h> int main() { pid_t pid1 = fork(); if (pid1 == -1) { perror("fork pid1 failed"); return EXIT_FAILURE; } if (pid1 == 0) { // 子进程1:正常终止,状态码 8 printf("子进程1(PID:%d)正常退出,状态码 8\n", getpid()); exit(8); } pid_t pid2 = fork(); if (pid2 == -1) { perror("fork pid2 failed"); return EXIT_FAILURE; } if (pid2 == 0) { // 子进程2:等待被信号终止 printf("子进程2(PID:%d)等待被 SIGKILL 终止...\n", getpid()); sleep(5); exit(EXIT_SUCCESS); } // 父进程回收子进程1(正常终止) int status; pid_t ret = waitpid(pid1, &status, 0); if (ret > 0) { printf("回收子进程1(PID:%d):\n", ret); if (WIFEXITED(status)) { printf(" - 终止类型:正常终止\n"); printf(" - 退出状态码:%d\n", WEXITSTATUS(status)); } } // 父进程发送 SIGKILL 终止子进程2 kill(pid2, SIGKILL); ret = waitpid(pid2, &status, 0); if (ret > 0) { printf("回收子进程2(PID:%d):\n", ret); if (WIFSIGNALED(status)) { printf(" - 终止类型:信号终止\n"); printf(" - 终止信号编号:%d\n", WTERMSIG(status)); } } return EXIT_SUCCESS; }

运行结果:

子进程1(PID:12345)正常退出,状态码 8 子进程2(PID:12346)等待被 SIGKILL 终止... 回收子进程1(PID:12345): - 终止类型:正常终止 - 退出状态码:8 回收子进程2(PID:12346): - 终止类型:信号终止 - 终止信号编号:9

四、进程终止后的特殊状态:僵尸进程与孤儿进程

父进程与子进程的终止顺序不同,会产生两种特殊的进程状态 —— 僵尸进程和孤儿进程。二者对系统资源的影响差异巨大,是 Linux 进程管理中必须重点关注的内容。

4.1 僵尸进程:子死父不埋,资源泄漏隐患

定义

父进程创建子进程后,子进程先终止,但父进程未调用wait()/waitpid()函数回收子进程的PCB(进程控制块),此时子进程的用户空间内存已释放,但内核空间的 PCB 仍存在,这类进程称为僵尸进程

危害

PCB 会占用内核内存资源,若父进程长期运行且频繁创建子进程,会导致系统中积累大量僵尸进程,耗尽内核内存,进而引发系统不稳定甚至崩溃。

查看方式

使用ps命令查看进程状态,僵尸进程的状态标记为Z

ps aux | grep Z

使用top命令时,僵尸进程的数量会显示在zombie列中。

解决方法:wait ()/waitpid () 主动回收

父进程通过调用wait()waitpid()函数,可主动回收子进程的 PCB,释放内核资源。二者的核心特性对比如下:

函数原型核心特性
wait()pid_t wait(int *status)阻塞等待任意一个子进程终止,回收其 PCB;失败返回 -1
waitpid()pid_t waitpid(pid_t pid, int *status, int options)可指定回收特定 PID 的子进程;支持非阻塞模式(WNOHANG),无待回收子进程时返回 0

waitpid () 非阻塞回收示例

#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> int main() { pid_t pid = fork(); if (pid == -1) { perror("fork failed"); return EXIT_FAILURE; } if (pid == 0) { // 子进程睡眠3秒后退出 printf("子进程(PID:%d)睡眠3秒后退出\n", getpid()); sleep(3); exit(10); } else { int status; while (1) { // 非阻塞模式回收指定子进程 pid_t ret = waitpid(pid, &status, WNOHANG); if (ret > 0) { // 成功回收 printf("回收子进程(PID:%d),状态码:%d\n", ret, WEXITSTATUS(status)); break; } else if (ret == 0) { // 暂无子进程退出,父进程执行其他任务 printf("暂无子进程退出,父进程执行其他任务...\n"); sleep(1); } else { // 回收失败 perror("waitpid failed"); break; } } } return EXIT_SUCCESS; }

4.2 孤儿进程:父死子无依,系统自动接管

定义

父进程创建子进程后,父进程先终止,子进程失去父进程,这类进程称为孤儿进程

处理机制

Linux 系统中,孤儿进程会被init 进程(PID=1)接管,init 进程会成为孤儿进程的新父进程。当孤儿进程终止时,init 进程会自动调用wait()函数回收其 PCB,因此孤儿进程不会像僵尸进程那样占用系统资源,通常无需人工干预。

代码示例:孤儿进程的产生与接管
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid = fork(); if (pid == -1) { perror("fork failed"); return EXIT_FAILURE; } if (pid == 0) { // 子进程 printf("子进程(PID:%d),原父进程(PID:%d)\n", getpid(), getppid()); sleep(3); // 等待父进程终止 printf("子进程(PID:%d),新父进程(PID:%d)\n", getpid(), getppid()); } else { // 父进程立即退出 printf("父进程(PID:%d)即将退出\n", getpid()); exit(EXIT_SUCCESS); } return EXIT_SUCCESS; }

运行结果:

父进程(PID:12347)即将退出 子进程(PID:12348),原父进程(PID:12347) 子进程(PID:12348),新父进程(PID:1)

五、总结

  1. 进程终止分为正常和异常两类共 8 种方式,核心差异在于是否主动退出、是否执行清理流程;
  2. main函数return依赖exit()完成清理,exit()执行全量清理后调用_exit()_exit()则直接终止进程;
  3. 父进程通过wait()/waitpid()结合解析宏,可获取子进程的终止状态并回收资源;
  4. 僵尸进程是 “子死父不埋”,需父进程主动回收;孤儿进程是 “父死子无依”,由 init 进程接管,无资源风险。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/2 21:18:56

零成本自制NAND闪存编程器:STM32开源硬件工具完全指南

零成本自制NAND闪存编程器&#xff1a;STM32开源硬件工具完全指南 【免费下载链接】nand_programmer NANDO - NAND Open programmer 项目地址: https://gitcode.com/gh_mirrors/na/nand_programmer 你是否曾经遇到过需要读取NAND闪存芯片却苦于没有专业编程工具的困境&a…

作者头像 李华
网站建设 2026/2/2 23:16:29

5大核心方法深度解析:YOLO系列模型标注格式转换完全指南

5大核心方法深度解析&#xff1a;YOLO系列模型标注格式转换完全指南 【免费下载链接】ultralytics ultralytics - 提供 YOLOv8 模型&#xff0c;用于目标检测、图像分割、姿态估计和图像分类&#xff0c;适合机器学习和计算机视觉领域的开发者。 项目地址: https://gitcode.c…

作者头像 李华
网站建设 2026/2/5 9:57:01

3分钟搞定Steam成就管理:让你的游戏之旅不留遗憾!

3分钟搞定Steam成就管理&#xff1a;让你的游戏之旅不留遗憾&#xff01; 【免费下载链接】SteamAchievementManager A manager for game achievements in Steam. 项目地址: https://gitcode.com/gh_mirrors/st/SteamAchievementManager 还在为那些永远无法解锁的Steam成…

作者头像 李华
网站建设 2026/2/5 9:07:24

如何用开源神器实现跨设备无缝操控?

如何用开源神器实现跨设备无缝操控&#xff1f; 【免费下载链接】lan-mouse mouse & keyboard sharing via LAN 项目地址: https://gitcode.com/gh_mirrors/la/lan-mouse 在数字化办公时代&#xff0c;多设备协同已成为技术爱好者和跨平台用户的刚需。Lan Mouse作为…

作者头像 李华
网站建设 2026/2/6 9:33:02

Luckysheet大数据处理性能调优全攻略:让百万行数据流畅运行

Luckysheet大数据处理性能调优全攻略&#xff1a;让百万行数据流畅运行 【免费下载链接】Luckysheet 项目地址: https://gitcode.com/gh_mirrors/luc/Luckysheet 你是否曾经遇到过这样的困扰&#xff1a;当表格数据量超过10万行时&#xff0c;页面加载缓慢、操作卡顿、…

作者头像 李华
网站建设 2026/2/2 23:16:57

如何快速掌握ExifToolGui:照片元数据管理的终极指南

如何快速掌握ExifToolGui&#xff1a;照片元数据管理的终极指南 【免费下载链接】ExifToolGui A GUI for ExifTool 项目地址: https://gitcode.com/gh_mirrors/ex/ExifToolGui 在数字摄影时代&#xff0c;照片管理不再只是简单的文件整理。每张照片背后都隐藏着丰富的元…

作者头像 李华