什么是僵尸进程

什么是僵尸进程

1. 僵尸进程的定义
  • 僵尸进程 是指那些已经终止执行(即已经调用了exit()或者完成了main()函数),但其父进程尚未通过wait()waitpid()系统调用获取其退出状态的进程。
  • 在进程的生命周期中,当一个子进程结束时,它会将其退出状态信息(包括退出码、资源使用情况等)发送给其父进程。如果父进程没有及时读取这些信息,子进程就会变成僵尸状态。
2. 僵尸进程的生命周期
  1. 创建阶段 :父进程通过fork()创建一个子进程。
  2. 执行阶段:子进程执行其任务。
  3. 终止阶段 :子进程完成任务后调用exit()终止。
  4. 僵尸阶段:子进程终止后,其进程号(PID)和退出状态被保留在进程表中,等待父进程读取。
  5. 清理阶段 :父进程调用wait()waitpid()读取子进程的退出状态,系统从进程表中移除僵尸进程。
3. 僵尸进程的原因
  • 父进程未调用wait()waitpid():当子进程终止时,父进程未能及时调用这些系统调用来获取子进程的退出状态,导致子进程信息无法被清理。
  • 父进程忽略SIGCHLD信号 :子进程终止时,内核会向父进程发送SIGCHLD信号,通知其有子进程已终止。如果父进程忽略或未处理该信号,子进程可能无法被及时回收。
  • 父进程自身终止 :如果父进程在子进程终止前就已结束,子进程将被init进程(PID为1的进程)收养。init会定期调用wait()来清理任何孤儿进程,包括僵尸进程。
4. 僵尸进程的影响
  • 资源占用:僵尸进程不会占用系统资源(如CPU和内存),因为它们已经终止。但它们仍然占用进程表中的一个条目,且保留了一些退出状态信息。
  • 进程表溢出:如果系统中积累大量僵尸进程,可能会耗尽进程表中的可用条目,导致新的进程无法创建。这虽然在现代系统中较为罕见,但在编写不当的应用程序中可能会发生。
5. 检测僵尸进程

使用ps命令可以查看系统中的僵尸进程。例如:

cpp 复制代码
ps aux | grep Z

在输出中,僵尸进程通常会显示为状态Z,例如:

cpp 复制代码
username  1234  0.0  0.0      0     0 ?        Z    10:00   0:00 [process_name] <defunct>
6. 处理僵尸进程

1. 确保父进程调用wait()waitpid()

在编写多进程程序时,父进程应确保在合适的时机调用wait()waitpid()来回收子进程。这可以通过以下方式实现:

cpp 复制代码
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>

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

    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    else if (pid == 0) {
        // 子进程执行
        printf("子进程PID: %d\n", getpid());
        exit(0);
    }
    else {
        // 父进程等待子进程结束
        int status;
        waitpid(pid, &status, 0);
        printf("子进程已结束,PID: %d\n", pid);
    }

    return 0;
}

2. 处理SIGCHLD信号

父进程可以通过设置信号处理函数来自动处理子进程的终止,从而避免僵尸进程的产生。例如:

cpp 复制代码
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <signal.h>

void sigchld_handler(int signo) {
    (void)signo; // 避免未使用参数的警告
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main() {
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    else if (pid == 0) {
        // 子进程执行
        printf("子进程PID: %d\n", getpid());
        exit(0);
    }
    else {
        // 父进程继续执行其他任务
        printf("父进程PID: %d,等待子进程终止...\n", getpid());
        // 模拟父进程执行
        sleep(5);
    }

    return 0;
}

3. 终止不必要的父进程

如果父进程不再需要对子进程的管理,可以让子进程成为init(PID为1)的子进程。init会自动回收子进程,防止僵尸进程的产生。可以通过"双重派生"(double-fork)技术实现:

cpp 复制代码
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>

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

    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    else if (pid == 0) {
        // 第一个子进程
        pid_t pid2 = fork();
        if (pid2 < 0) {
            perror("fork");
            exit(EXIT_FAILURE);
        }
        else if (pid2 > 0) {
            // 第一个子进程退出
            exit(0);
        }
        else {
            // 第二个子进程成为孤儿进程,被init收养
            printf("孤儿进程PID: %d\n", getpid());
            // 执行任务
            sleep(10);
            exit(0);
        }
    }
    else {
        // 父进程等待第一个子进程退出
        wait(NULL);
        // 父进程继续执行
        printf("父进程继续执行...\n");
        sleep(15);
    }

    return 0;
}
7. 示例:僵尸进程的产生与消除

1. 创建僵尸进程

以下示例展示了如何产生一个僵尸进程。在该示例中,父进程创建子进程后不调用wait(),导致子进程在终止后变成僵尸状态。

cpp 复制代码
// zombie_example.cpp
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>

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

    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    else if (pid == 0) {
        // 子进程执行
        printf("子进程PID: %d,即将终止。\n", getpid());
        exit(0);
    }
    else {
        // 父进程不调用 wait(),睡眠以保留子进程为僵尸状态
        printf("父进程PID: %d,子进程PID: %d 已创建。\n", getpid(), pid);
        printf("父进程进入睡眠,不调用 wait()。\n");
        sleep(30);
    }

    return 0;
}

编译与运行:

bash 复制代码
g++ zombie_example.cpp -o zombie_example
./zombie_example

观察僵尸进程:

在父进程睡眠期间,使用ps命令查看系统中的僵尸进程:

bash 复制代码
ps aux | grep Z

输出:

php-template 复制代码
username  1234  0.0  0.0      0     0 ?        Z    10:00   0:00 [zombie_example] <defunct>

2. 消除僵尸进程

要消除僵尸进程,可以通过以下方法之一:

让父进程调用wait()waitpid() :修改父进程代码,使其在子进程结束后调用wait(),如下:

cpp 复制代码
// 修改后的父进程部分
else {
    printf("父进程PID: %d,子进程PID: %d 已创建。\n", getpid(), pid);
    printf("父进程等待子进程终止。\n");
    waitpid(pid, NULL, 0);
    printf("子进程已被回收。\n");
}

这将确保子进程在终止后不再处于僵尸状态。

  • 使用信号处理 :如前文所述,通过设置SIGCHLD信号处理函数,自动回收子进程。
  • 终止父进程 :如果父进程终止,僵尸子进程会被init进程收养,init会调用wait()回收其退出状态,从而消除僵尸进程。
8. 总结
  • 僵尸进程是已经终止但其资源尚未被完全释放的进程,主要原因是父进程未及时回收其退出状态。
  • 影响:虽不直接占用资源,但大量僵尸进程可能导致系统资源耗尽。
  • 预防与处理
    • 父进程应及时调用wait()waitpid()回收子进程。
    • 使用信号处理机制自动回收子进程。
    • 使用双重派生技术避免僵尸进程的产生。

理解和正确处理僵尸进程对于维护系统的稳定性和可靠性至关重要,特别是在开发需要频繁创建和管理子进程的应用程序时。

相关推荐
文城5218 分钟前
Mysql存储过程(学习自用)
数据库·学习·mysql
沉默的煎蛋14 分钟前
MyBatis 注解开发详解
java·数据库·mysql·算法·mybatis
呼啦啦啦啦啦啦啦啦14 分钟前
【Redis】事务
数据库·redis·缓存
HaoHao_01016 分钟前
AWS Serverless Application Repository
服务器·数据库·云计算·aws·云服务器
C语言扫地僧31 分钟前
MySQL 事务及MVCC机制详解
数据库·mysql
小镇cxy33 分钟前
MySQL事物,MVCC机制
数据库·mysql
Golinie39 分钟前
【C++高并发服务器WebServer】-2:exec函数簇、进程控制
linux·c++·webserver·高并发服务器
励志去大厂的菜鸟1 小时前
系统相关类——java.lang.Math (三)(案例详细拆解小白友好)
java·服务器·开发语言·深度学习·学习方法
老王聊主机1 小时前
2025年华为云一键快速部署幻兽帕鲁联机服务器教程
运维·服务器·华为云
课堂随想1 小时前
`std::make_shared` 无法直接用于单例模式,因为它需要访问构造函数,而构造函数通常是私有的
c++·单例模式