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

僵尸进程是什么?怎么回收?孤儿进程?

僵尸进程是什么?

  1. 僵尸进程的定义:对于多进程程序,当子进程结束运行但父进程还未读取其退出状态时,子进程就处于僵尸态。此时,内核不会立即释放该子进程的进程表表项,以满足父进程后续查询子进程退出信息的需求
  2. 产生原因:子进程运行结束后,父进程若没有及时获取其退出状态,子进程就会一直处于僵尸态;当父进程提前结束或异常终止,而子进程继续运行时,子进程的 PPID(父进程 ID)会被操作系统设置为 1,即由 init 进程接管。在父进程退出后到子进程退出前这段时间,子进程也处于僵尸态。
  3. 僵尸进程的危害:僵尸进程会一直占据内核资源,而内核资源是有限的。如果大量产生僵尸进程,可能会导致系统资源耗尽,影响系统性能。

处理僵尸进程的函数

        pid_t wait(int *stat_loc)

        wait 函数会阻塞调用它的进程(通常是父进程),直到该进程的任意一个子进程结束运行。这意味着调用 wait 后,父进程会暂停执行,等待子进程完成任务。
        当有子进程结束时,wait 函数返回结束运行的子进程的进程 ID(PID),同时将该子进程的退出状态信息存储在 stat_loc 参数指向的内存位置。退出状态信息包含了子进程是如何结束的,例如是正常退出还是因信号终止等。
        通过 sys/wait.h 头文件中定义的宏来解析 stat_loc 中的退出状态信息:

  • WIFEXITED(stat_val):用于判断子进程是否正常结束。如果子进程正常结束,该宏返回一个非零值(即真)。
  • WEXITSTATUS(stat_val):当 WIFEXITED 返回非零值时,使用此宏可以获取子进程的退出码。子进程通过 exit 函数或从 main 函数返回时设置的退出码,可以通过这个宏获取。
  • WIFSIGNALED(stat_val):如果子进程是因为一个未捕获的信号而终止,此宏返回一个非零值。
  • WTERMSIG(stat_val):当 WIFSIGNALED 返回非零值时,该宏返回导致子进程终止的信号值。例如,如果子进程因接收到 SIGTERM 信号而终止,WTERMSIG 将返回 SIGTERM 的值。
  • WIFSTOPPED(stat_val):若子进程被暂停(例如收到 SIGSTOP 信号),此宏返回一个非零值。
  • WSTOPSIG(stat_val):当 WIFSTOPPED 返回非零值时,该宏返回导致子进程暂停的信号值。

        示例代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid;int status;pid = fork();if (pid == -1) {perror("fork");return 1;} else if (pid == 0) {// 子进程printf("Child process: My PID is %d\n", (int)getpid());exit(10); // 子进程正常退出,退出码为10} else {// 父进程pid_t terminated_pid = wait(&status);if (terminated_pid == -1) {perror("wait");return 1;}if (WIFEXITED(status)) {printf("Child %d exited normally with exit code %d\n", (int)terminated_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child %d terminated by signal %d\n", (int)terminated_pid, WTERMSIG(status));}}return 0;
}

        输出如下:

        pid_t waitpid(pid_t pid, int *stat_loc, int options)

   waitpid 函数提供了比 wait 函数更灵活的等待方式。它可以等待由 pid 参数指定的特定子进程。

  • pid > 0:等待进程 ID 为 pid 的子进程。
  • pid = -1:等待任意一个子进程,此时 waitpid 的行为和 wait 函数相同。
  • pid = 0:等待与调用进程同组的任意子进程。
  • pid < -1:等待进程组 ID 等于 pid 绝对值的任意子进程。

        与 wait 函数类似,waitpid 函数返回结束运行的子进程的 PID,并将子进程的退出状态信息存储在 stat_loc 参数指向的内存位置,同样可以使用 sys/wait.h 中的宏来解析退出状态。
        options 参数可以控制 waitpid 函数的行为。最常用的取值是 WNOHANG,当 options 取值为 WNOHANG 时,waitpid 调用将是非阻塞的。如果 pid 指定的目标子进程还没有结束或意外终止,则 waitpid 立即返回 0;如果目标子进程确实正常退出了,则 waitpid 返回该子进程的 PID。若 waitpid 调用失败,返回 -1 并设置 errno

   示例代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid;int status;pid = fork();if (pid == -1) {perror("fork");return 1;} else if (pid == 0) {// 子进程printf("Child process: My PID is %d\n", (int)getpid());sleep(2); // 模拟子进程执行一些任务exit(10); // 子进程正常退出,退出码为10} else {// 父进程while (1) {pid_t terminated_pid = waitpid(pid, &status, WNOHANG);if (terminated_pid == -1) {perror("waitpid");return 1;} else if (terminated_pid == 0) {// 子进程还未结束printf("Child is still running...\n");sleep(1);//模拟父进程处理自己的任务} else {if (WIFEXITED(status)) {printf("Child %d exited normally with exit code %d\n", (int)terminated_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child %d terminated by signal %d\n", (int)terminated_pid, WTERMSIG(status));}break;}}}return 0;
}

        输出如下:
 

        讲解一下示例代码:采用的是轮询的方式检测子进程是否已经退出,如果未退出waitpid()会返回0,进行下次轮检测;如果子进程退出了,会打印子进程的退出码。这样做的好处是,父进程不必阻塞等待子进程退出,它可以边等待边处理自己的任务。

处理僵尸进程更好的方式:利用SIGCHLD信号

        不断的轮询子进程的状态,绝非明智之举。要在事件(当然了这里的事件就是子进程退出)已经发生的情况下,执行非阻塞调用才能提高程序的效率。
        当一个进程结束时,它会给其父进程发送一个SIGCHLD信号。因此,父进程可以捕获这个信号,并在信号处理函数中调用waitpid函数来处理结束的子进程,从而避免僵尸进程的产生。也就是说:SIGCHLD信号触发,相当于通知父进程,你的子进程已经退出了。

   示例代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>// SIGCHLD信号处理函数
static void handle_child(int sig) {pid_t pid;int stat;// 使用waitpid循环处理已结束的子进程while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) {if (WIFEXITED(stat)) {printf("Child %d exited normally with exit code %d\n", (int)pid, WEXITSTATUS(stat));} else if (WIFSIGNALED(stat)) {printf("Child %d terminated by signal %d\n", (int)pid, WTERMSIG(stat));}}
}int main() {pid_t pid;// 注册SIGCHLD信号处理函数struct sigaction sa;sa.sa_handler = handle_child;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART;if (sigaction(SIGCHLD, &sa, NULL) == -1) {perror("sigaction");exit(1);}// 创建子进程pid = fork();if (pid == -1) {perror("fork");exit(1);} else if (pid == 0) {// 子进程printf("Child process: My PID is %d\n", (int)getpid());sleep(2); // 模拟子进程执行一些任务exit(10); // 子进程正常退出,退出码为10} else {// 父进程printf("Parent process: My PID is %d, Child PID is %d\n", (int)getpid(), (int)pid);// 父进程可以继续执行其他任务while (1) {printf("Parent is doing other things...\n");sleep(1);}}return 0;
}

        输出结果如下:

        对于初学者来说看这段代码有些吃力,建议大家在具备了进程信号的知识的前提下再进行阅读。

什么是孤儿进程?

        孤儿进程指的是父进程在子进程之前终止,导致子进程失去了父进程的管理和控制。此时,这些子进程会被 init 进程(进程 ID 为 1)收养。init 进程是 Linux 系统启动后创建的第一个用户态进程,它负责管理系统中所有孤儿进程的生命周期。
        如果父进程执行完毕或因异常情况提前终止,而它创建的子进程还在运行,这些子进程就会成为孤儿进程。
        写段代码举个例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>int main() {pid_t pid = fork();if (pid == -1) {perror("fork");return 1;} else if (pid == 0) {// 子进程printf("Child process: My PID is %d, My PPID is %d\n", (int)getpid(), (int)getppid());sleep(5); // 模拟子进程执行任务printf("Child process: After sleeping, My PPID is %d\n", (int)getppid());} else {// 父进程printf("Parent process: My PID is %d, Child PID is %d\n", (int)getpid(), (int)pid);// 父进程提前结束return 0;}return 0;
}

        输出结果如下:

http://www.xdnf.cn/news/924121.html

相关文章:

  • vue3: bingmap using typescript
  • 快速上手shell脚本运行流程控制
  • 深度相机的日常学习
  • 20250607-在Ubuntu中使用Anaconda创建新环境并使用本地的备份文件yaml进行配置
  • Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(上)
  • 线程安全集合
  • JUC并发编程(五)volatile/可见性/原子性/有序性->JMM
  • 基于 GWAS 的群体遗传分析将 bZIP29 确定为玉米中的异种基因
  • QT学习教程(二十一)
  • redis主从复制
  • go中的接口返回设计思想
  • AI Agent 与 Agentic AI 企业实践
  • 湖北理元理律师事务所:债务优化中的民生保障实践
  • 【C/C++】std::vector成员函数清单
  • 力扣HOT100之二分查找:33. 搜索旋转排序数组
  • Docke启动Ktransformers部署Qwen3MOE模型实战与性能测试
  • 如何理解ES6模块化方案的缓存机制?
  • SpringBoot离线应用的5种实现方式
  • 【python】RGB to YUV and YUV to RGB
  • 使用python实现奔跑的线条效果
  • 【八股消消乐】MySQL存储引擎InnoDB知识点汇总
  • 深入解析快速排序算法:原理、优化与应用
  • java内存模型JMM
  • 图上合成:用于大型语言模型持续预训练的知识合成数据生成
  • Python: 告别 ModuleNotFoundError, 解决 pipx 环境下 sshuttle 缺少 pydivert 依赖的终极指南
  • Redis Key过期策略
  • 关于 ​​Thread 与 Runnable​​ 以及 ​​线程生命周期​​ 的详细说明与示例
  • Protobuf 中的类型查找规则
  • ADB识别手机系统弹授权框-如何处理多重弹框叠加和重叠问题
  • 现代C++特性(一):基本数据类型扩展