僵尸进程

本文介绍了僵尸进程的概念及其产生的原因,展示了如何通过使用wait()、waitpid()函数或信号机制避免僵尸进程的产生,并提供了具体的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果一个进程已经退出,但是占用的资源未被回收,则称这样的进程为僵尸进程。很容易想到,如果子进程退出后,父进程未用 wait() 或 waitpid() 去回收子进程的资源,就会形成僵尸进程。如果一个程序循环创建子进程,但忘记回收退出的子进程的资源。那么这些子进程就会一直占用进程号。而系统的进程号是有限的,这个程序长时间运行后就会耗尽系统的进程号,从而导致无法产生新的进程。

代码

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return 0;
    }

    if(pid == 0)
    {
        printf("子进程:%d\n",getpid());
        /* 子进程退出 */
        exit(0);
    }
    else if(pid > 0)
    {
        /* 父进程不使用 wait() 或 waitpid() */
        printf("父进程:%d\n",getpid());
    }
    /* 查看系统中的僵尸进程 */
    system("ps -ef | grep defunct");
    return 0;
}
[lingyun@manjaro study]$ gcc study.c 
[lingyun@manjaro study]$ ./a.out
父进程:11125
子进程:11126
lingyun  11126 11125  0 21:57 pts/1    00:00:00 [a.out] <defunct>
lingyun  11127 11125  0 21:57 pts/1    00:00:00 sh -c ps -ef | grep defunct
lingyun  11129 11127  0 21:57 pts/1    00:00:00 grep defunct

可以看到,子进程 11126 是一个僵尸进程。注意:如果父进程也结束了,那么子进程的资源就会被回收。(不清楚啥机制,实测得到的结果)

避免僵尸进程

当然,最直接的方式就是在父进程中使用 wait() 或 waitpid() 去回收子进程的资源。但这对父进程来说太浪费了。我们可以用信号机制来解决。子进程退出时会向父进程发送 SIGCHLD 信号,我们就可以使用 signal() 函数来处理它。只要子进程退出,就自动调用回调函数,在回调函数里调用 wait() 或 waitpid() 即可。

代码

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>

void signal_callback(int signo)
{
    pid_t pid;
    /* 用 while 可以处理多个子进程退出 */
    while((pid = waitpid(-1,NULL,WNOHANG)) > 0)
    {
        printf("回收的子进程为:%d\n",pid);
    }
}

int main()
{
    /* 设置捕捉子进程的信号,只有接收到信号 SIGCHLD 便自动调用 signal_callback 函数 */
    signal(SIGCHLD,signal_callback);

    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    if(pid == 0)
    {
        printf("子进程:%d\n",getpid());
        exit(0);
    }
    else if(pid > 0)
    {
        /* 让子进程先结束 */
        sleep(5);
        printf("父进程:%d\n",getpid());
        system("ps -ef | grep defunct");
    }
    return 0;
}
[lingyun@manjaro study]$ gcc study.c 
[lingyun@manjaro study]$ ./a.out
子进程:16392
回收的子进程为:16392
父进程:16391
lingyun  16393 16391  0 22:30 pts/1    00:00:00 sh -c ps -ef | grep defunct
lingyun  16395 16393  0 22:30 pts/1    00:00:00 grep defunct

可以看到子进程退出后并未产生僵尸进程,说明资源已经被回收了。

如果子进程的状态对父进程没有用,那么可以用 signal(SIGCHLD, SIG_IGN) 通知内核去处理退出的子进程,回收资源,不会再向父进程发送信号。

代码

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
/* 
void signal_callback(int signo)
{
    pid_t pid;
    while((pid = waitpid(-1,NULL,WNOHANG)) > 0)
    {
        printf("回收的子进程为:%d\n",pid);
    }
}
*/
int main()
{
    /* 设置捕捉子进程的信号,子进程结束后,内核会回收,并不再给父进程发送信号 */
    signal(SIGCHLD,SIG_IGN);

    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    if(pid == 0)
    {
        printf("子进程:%d\n",getpid());
        exit(0);
    }
    else if(pid > 0)
    {
        /* 让子进程先结束 */
        sleep(5);
        printf("父进程:%d\n",getpid());
        system("ps -ef | grep defunct");
    }
    return 0;
}
[lingyun@manjaro study]$ gcc study.c 
[lingyun@manjaro study]$ ./a.out
子进程:18167
父进程:18166
lingyun  18168 18166  0 22:41 pts/1    00:00:00 sh -c ps -ef | grep defunct
lingyun  18170 18168  0 22:41 pts/1    00:00:00 grep defunct

可见,并未产生僵尸进程,这样的处理方式也可以实现僵尸进程的避免。

还有一种方式可以做到这一点,就是制造孤儿进程,孤儿进程的父进程是 init 进程(1号进程),这样子进程退出后,init 进程就会回收其资源,同样也可避免僵尸进程。今儿太累了,不学了,吃不消,撤了。

Netty 是一个高性能的网络编程框架,广泛用于构建服务器端和客户端应用程序。然而,在某些情况下,使用 Netty 可能会导致僵尸进程(Zombie Process)的出现。僵尸进程是指已经终止但其父进程尚未调用 `wait()` 或 `waitpid()` 来获取其终止状态的进程。这类进程仍然占用系统资源(如进程 ID),如果数量过多,可能会影响系统的稳定性和性能。 ### 僵尸进程的成因 在 Netty 中,僵尸进程的产生通常与以下因素有关: - **子进程的创建**:Netty 本身并不直接创建子进程,但在某些场景下,比如与外部命令交互、使用 `ProcessBuilder` 或 `Runtime.exec()` 启动子进程,可能会导致僵尸进程的产生。如果父进程没有正确等待子进程退出状态,子进程就会变成僵尸进程。 - **未处理的信号**:在 Unix/Linux 系统中,当子进程终止,会向父进程发送 `SIGCHLD` 信号。如果父进程没有正确处理该信号,或者忽略了 `SIGCHLD` 信号的处理,子进程的状态将不会被回收,从而导致僵尸进程的产生 [^1]。 ### 僵尸进程的排查 要排查 Netty 应用中是否存在僵尸进程问题,可以通过以下几种方式进行: 1. **使用 `ps` 命令**:通过 `ps -ef | grep defunct` 可以查看当前系统中的僵尸进程僵尸进程的状态通常显示为 `<defunct>`。 2. **检查进程树**:可以使用 `pstree` 命令来查看进程树,确认哪些进程是僵尸进程,并追踪它们的父进程。 3. **监控系统资源**:使用 `top` 或 `htop` 等工具监控系统的进程状态,观察是否有大量僵尸进程存在。 ### 解决方案 为了防止 Netty 应用中出现僵尸进程,可以采取以下措施: 1. **确保子进程被正确回收**:在创建子进程,确保父进程调用了 `wait()` 或 `waitpid()` 来回收子进程的状态。如果使用 Java 的 `ProcessBuilder` 或 `Runtime.exec()` 创建子进程,可以通过调用 `process.waitFor()` 来等待子进程的结束 [^1]。 ```java Process process = Runtime.getRuntime().exec("some command"); int exitCode = process.waitFor(); ``` 2. **忽略 `SIGCHLD` 信号**:在某些情况下,可以考虑忽略 `SIGCHLD` 信号,这样系统会自动回收子进程的状态,避免僵尸进程的产生。可以通过 `Signal` 类来忽略信号 [^1]。 ```java Signal.handle(new Signal("CHLD"), signal -> {}); ``` 3. **使用守护线程**:如果子进程是由某个线程启动的,可以将该线程设置为守护线程,这样即使子进程没有被正确回收,也不会导致僵尸进程的长期存在。 4. **使用第三方库**:考虑使用更高级别的库来管理子进程,例如 Apache Commons Exec,它提供了更强大的功能来处理子进程的生命周期 [^1]。 5. **定期清理僵尸进程**:在某些情况下,可以通过编写脚本或使用工具定期清理僵尸进程。例如,使用 `kill -9 <pid>` 来强制终止僵尸进程,但这并不是推荐的做法,因为这可能会导致资源泄漏。 ### 总结 虽然 Netty 本身不会直接导致僵尸进程的产生,但在与外部命令交互或创建子进程,如果不注意进程的生命周期管理,就可能出现僵尸进程问题。通过合理的设计和管理,可以有效避免此类问题的发生,确保系统的稳定性和性能。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值