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

【僵尸进程】

【僵尸进程】

  • 目录:
      • 知识点
        • 1. 僵尸进程的定义
        • 2. 僵尸进程产生的原因
        • 3. 僵尸进程的危害
        • 4. 如何避免僵尸进程
      • 代码示例
        • 产生僵尸进程的代码示例
        • 避免僵尸进程的代码示例(父进程主动回收)
        • 避免僵尸进程的代码示例(信号处理)
      • 运行结果
        • 产生僵尸进程的代码运行结果
        • 避免僵尸进程的代码(父进程主动回收)运行结果
        • 避免僵尸进程的代码(信号处理)运行结果
    • 僵尸进程
      • 原理
      • 实现方法
        • 1. 使用 `wait` 函数
        • 2. 使用 `waitpid` 函数
      • 代码示例
        • 使用 `wait` 函数
        • 使用 `waitpid` 函数
      • 代码解释
      • 会自动释放的资源
        • 1. 内存资源
        • 2. 文件描述符
        • 3. 信号处理函数设置
      • 不会自动释放的资源
        • 1. 进程描述符
        • 2. 一些共享资源

目录:

知识点

1. 僵尸进程的定义

在 Linux 系统中,当一个子进程结束运行(通过调用 exit 或接收到终止信号),它并不会立即从系统中消失。子进程会进入僵尸状态(Zombie State),此时子进程已经停止执行,但它的进程描述符(包含进程的退出状态等信息)仍然保留在系统中,直到其父进程调用 waitwaitpid 等系统调用获取其退出状态,释放该进程描述符,子进程才会真正被销毁。处于这种状态的进程就被称为僵尸进程

2. 僵尸进程产生的原因

子进程先于父进程结束,而父进程没有及时调用 waitwaitpid 来回收子进程的资源,导致子进程的进程描述符一直保留在系统中,从而产生僵尸进程。

3. 僵尸进程的危害

僵尸进程虽然不占用系统的 CPU 资源,但它会占用系统的进程表项。如果系统中存在大量的僵尸进程,会导致进程表项被耗尽,从而无法创建新的进程,影响系统的正常运行

4. 如何避免僵尸进程
  • 父进程主动回收:父进程在子进程结束后,及时调用 waitwaitpid 来回收子进程的资源。
  • 信号处理:父进程可以通过捕获 SIGCHLD 信号,在信号处理函数中调用 waitwaitpid 来回收子进程的资源。
  • 使用 sigaction 函数:结合 sigaction 函数和 SA_NOCLDWAIT 标志,避免子进程成为僵尸进程。

代码示例

产生僵尸进程的代码示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("子进程 (PID=%d) 即将退出\n", getpid());
        exit(0);
    } else {
        // 父进程
        printf("父进程 (PID=%d) 继续运行,不回收子进程资源\n", getpid());
        sleep(60); // 父进程休眠 60 秒,不回收子进程资源
    }

    return 0;
}
避免僵尸进程的代码示例(父进程主动回收)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("子进程 (PID=%d) 即将退出\n", getpid());
        exit(0);
    } else {
        // 父进程
        printf("父进程 (PID=%d) 等待子进程退出并回收资源\n", getpid());
        int status;
        pid_t child_pid = wait(&status);
        if (child_pid > 0) {
            if (WIFEXITED(status)) {
                printf("子进程 (PID=%d) 正常退出,退出状态码: %d\n", child_pid, WEXITSTATUS(status));
            }
        }
    }

    return 0;
}
避免僵尸进程的代码示例(信号处理)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void sigchld_handler(int signum) {
    int status;
    pid_t pid;
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        if (WIFEXITED(status)) {
            printf("子进程 (PID=%d) 正常退出,退出状态码: %d\n", pid, WEXITSTATUS(status));
        }
    }
}

int main() {
    signal(SIGCHLD, sigchld_handler);

    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("子进程 (PID=%d) 即将退出\n", getpid());
        exit(0);
    } else {
        // 父进程
        printf("父进程 (PID=%d) 继续运行,等待子进程退出信号\n", getpid());
        sleep(60);
    }

    return 0;
}

运行结果

产生僵尸进程的代码运行结果
子进程 (PID=1234) 即将退出
父进程 (PID=1233) 继续运行,不回收子进程资源

在父进程休眠的 60 秒内,使用 ps -ef | grep Z 命令可以查看到僵尸进程:

USER       PID  PPID  C STIME TTY          TIME CMD
youruser  1234  1233  0 10:00 pts/0    00:00:00 [a.out] <defunct>

其中 <defunct> 表示该进程是僵尸进程。

避免僵尸进程的代码(父进程主动回收)运行结果
子进程 (PID=1234) 即将退出
父进程 (PID=1233) 等待子进程退出并回收资源
子进程 (PID=1234) 正常退出,退出状态码: 0

此时使用 ps -ef | grep Z 命令不会查看到僵尸进程。

避免僵尸进程的代码(信号处理)运行结果
子进程 (PID=1234) 即将退出
父进程 (PID=1233) 继续运行,等待子进程退出信号
子进程 (PID=1234) 正常退出,退出状态码: 0

同样,使用 ps -ef | grep Z 命令不会查看到僵尸进程。

僵尸进程

在 Linux 系统中,父进程可以通过主动回收子进程资源的方式来避免僵尸进程的产生。下面为你详细介绍相关原理、实现方法以及代码示例。

原理

当子进程结束时,它会向父进程发送 SIGCHLD 信号,同时进入僵尸状态,此时子进程的进程描述符仍然保留在系统中,占用系统资源。父进程可以通过调用 waitwaitpid 系统调用获取子进程的退出状态,并释放其进程描述符,从而让子进程彻底从系统中消失

实现方法

1. 使用 wait 函数

wait 函数会阻塞父进程,直到它的任意一个子进程结束。当子进程结束后,wait 函数会返回该子进程的进程 ID,并将子进程的退出状态存储在 status 参数中。

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

pid_t wait(int *status);
  • 参数status 是一个指向整数的指针,用于存储子进程的退出状态。如果不需要获取退出状态,可以将其设置为 NULL
  • 返回值:成功时返回结束的子进程的进程 ID;出错时返回 -1。
2. 使用 waitpid 函数

waitpid 函数提供了更灵活的子进程回收方式,它可以指定要等待的子进程,还可以设置非阻塞模式

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

pid_t waitpid(pid_t pid, int *status, int options);
  • 参数
    • pid:指定要等待的子进程。取值有以下几种情况:
      • pid > 0:等待进程 ID 为 pid 的子进程。
      • pid == -1:等待任意一个子进程,等同于 wait 函数。
      • pid == 0:等待与调用进程同组的任意子进程。
      • pid < -1:等待进程组 ID 等于 pid 绝对值的任意子进程。
    • status:用于存储子进程的退出状态,与 wait 函数的 status 参数相同。
    • options:可以设置一些选项,常用的选项有:
      • WNOHANG:如果没有子进程结束,函数立即返回 0,而不会阻塞。
      • WUNTRACED:如果子进程处于暂停状态,函数也会返回。
      • WCONTINUED:如果子进程因收到 SIGCONT 信号而继续运行,函数也会返回。
  • 返回值
    • 如果 options 中设置了 WNOHANG,且没有子进程结束,返回 0。
    • 成功时返回结束的子进程的进程 ID。
    • 出错时返回 -1。

代码示例

使用 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");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("子进程 (PID=%d) 即将退出\n", getpid());
        exit(0);
    } else {
        // 父进程
        printf("父进程 (PID=%d) 等待子进程退出并回收资源\n", getpid());
        int status;
        pid_t child_pid = wait(&status);
        if (child_pid > 0) {
            if (WIFEXITED(status)) {
                printf("子进程 (PID=%d) 正常退出,退出状态码: %d\n", child_pid, WEXITSTATUS(status));
            }
        }
    }

    return 0;
}
使用 waitpid 函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("子进程 (PID=%d) 即将退出\n", getpid());
        exit(0);
    } else {
        // 父进程
        printf("父进程 (PID=%d) 等待子进程退出并回收资源\n", getpid());
        int status;
        pid_t child_pid = waitpid(pid, &status, 0);
        if (child_pid > 0) {
            if (WIFEXITED(status)) {
                printf("子进程 (PID=%d) 正常退出,退出状态码: %d\n", child_pid, WEXITSTATUS(status));
            }
        }
    }

    return 0;
}

代码解释

在上述代码中,首先使用 fork 函数创建一个子进程。

  • 子进程打印一条信息后调用 exit 函数退出。
  • 父进程打印一条信息后,调用 waitwaitpid 函数等待子进程退出,并获取其退出状态。
  • 使用 WIFEXITED 宏判断子进程是否正常退出,如果是,则使用 WEXITSTATUS 宏获取子进程的退出状态码并打印。

通过这种方式,父进程可以及时回收子进程的资源,避免僵尸进程的产生
子进程在退出时会自动释放一部分资源,但并非所有资源都会自动释放,下面为你详细介绍:

会自动释放的资源

1. 内存资源
  • 栈和堆内存:子进程在运行过程中在栈上分配的局部变量和在堆上通过malloccallocrealloc等函数分配的内存,在子进程退出时会被自动释放。例如,在子进程中使用malloc分配了一块内存用于存储数据,当子进程退出时,操作系统会回收这块内存,防止内存泄漏。
  • 数据段和代码段:子进程的代码段(存储程序的可执行指令)和数据段(存储全局变量和静态变量)所占用的内存空间也会在子进程退出时被释放。
    在这里插入图片描述
2. 文件描述符

子进程在运行过程中打开的文件描述符(如文件、套接字等),在子进程退出时会被操作系统自动关闭 ,例如,子进程打开了一个文件进行读写操作,当子进程退出时,该文件描述符会被关闭,相应的文件资源会被释放。

3. 信号处理函数设置

子进程所设置的信号处理函数等相关设置在其退出时会失效,操作系统会清理这些与子进程相关的信号处理信息。

不会自动释放的资源

1. 进程描述符

子进程退出后,其进程描述符(包含进程的状态信息、退出状态、资源使用统计等)并不会自动释放。子进程会进入僵尸状态(Zombie State),此时它的进程描述符仍然保留在系统中,直到其父进程调用waitwaitpid等系统调用获取其退出状态,释放该进程描述符,子进程才会真正从系统中消失。如果父进程没有及时回收子进程的进程描述符,就会产生僵尸进程

2. 一些共享资源
  • 共享内存:如果子进程使用了共享内存(通过shmgetshmat等函数创建或关联),在子进程退出时,共享内存本身不会自动释放。共享内存是多个进程可以共享的内存区域,子进程的退出不会影响其他进程对共享内存的使用,需要显式地调用shmdt(分离共享内存)和shmctl(删除共享内存)等函数来释放共享内存资源
  • 信号量:子进程使用的信号量(通过semgetsemop等函数操作)在其退出时也不会自动释放。信号量是用于进程间同步的机制,需要显式地调用semctl等函数来删除信号量集。

综上所述,虽然子进程在退出时会自动释放一部分资源,但对于进程描述符和一些共享资源,需要父进程或其他进程进行显式的处理,以确保系统资源的正确释放和回收。


http://www.kler.cn/a/559573.html

相关文章:

  • socket编程详解
  • RK Android11 WiFi模组 AIC8800 驱动移植流程
  • redis的容器化部署
  • YOLOv5 的量化及部署 - RGB 专题
  • 在单片机中是否应该取消32.768kHz外部晶振
  • FPGA开发要学些什么?如何快速入门?
  • RK3568开发板/电脑/ubuntu处于同一网段互通
  • UE_C++ —— Gameplay Tags
  • PyTorch torch.logsumexp 详解:数学原理、应用场景与性能优化(中英双语)
  • Docker 镜像操作笔记
  • Python--函数进阶(上)
  • Python 依赖包管理工具:uv
  • AI(14)-prompt
  • scrapy pipelines过滤重复数据
  • FPGA中利用fifo时钟域转换---慢时钟域转快时钟域
  • 三级分类bug解决
  • YOLOv11-ultralytics-8.3.67部分代码阅读笔记-loaders.py
  • nextjs项目搭建——头部导航
  • 如何使用Python快速开发一个带管理系统界面的网站-解析方案
  • 【DeepSeek-R1背后的技术】系列十一:RAG原理介绍和本地部署(DeepSeek+RAGFlow构建个人知识库)