Linux 僵尸进程的产生和销毁

Linux 僵尸进程的产生和销毁

Linux中用fork函数创建子进程。例如:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    pid_t pid;
    pid = fork();
    if(pid==0){
        printf("子进程执行区域\n");
    }else{
        printf("父进程执行区域\n");
    }
  
    printf("子进程和父进程都将执行该代码\n");
    return 0;
}

1、僵尸进程产生原因

1.1 父进程忙碌

出现僵尸进程的一个很重要的原因就是子进程已经执行完成,但父进程还在执行或处于休眠状态,且没有主动释放子进程的资源,此时该子进程就会变成僵尸进程。因为Linux系统中,子进程资源必须由父进程来释放,或是父进程结束,子进程才会结束。

test-fork.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
    pid_t pid;

    pid = fork();
    if(pid==0){
        printf("子进程执行区域\n");
      	exit(0);
    }else{
        while(1){
            printf("父进程执行区域\n");
        }
    }
    printf("子进程和父进程都将执行该代码\n");
    return 0;
}

运行结果

$ps au
ubuntu    29768  0.0  0.0   4512   728 pts/5    S+   08:21   0:00 ./test-fork
ubuntu    29769  0.0  0.0      0     0 pts/5    Z+   08:21   0:00 [test-fork] <defunct>

# 可见父进程在忙碌,没有及时回收子进程资源,子进程就变成僵尸进程

上述代码,子进程输出了一句话后就调用exit(0)正常退出进程,但其占用的进程ID还没释放,等着父进程主动释放,但父进程还处于忙碌状态,一直在循环输出,没有主动释放子进程ID。这这种状态下,该子进程就成为僵尸进程。

系统中能使用的进程ID是有限的,应该避免僵尸进程的产生。

1.2 父进程处于休眠状态

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
    // #include <stdlib.h>
    // #include <sys/wait.h>
    int main()
{
    pid_t pid;

    pid = fork();
    if(pid==0){
        printf("子进程执行区域\n");
        exit(0);
    }else{
        printf("父进程执行区域\n");
        sleep(12);
    }
    printf("子进程和父进程都将执行该代码\n");
    return 0;
}

运行结果

$ ps au
ubuntu    31179  0.0  0.0   4512   772 pts/5    S+   08:27   0:00 ./test-fork
ubuntu    31180  0.0  0.0      0     0 pts/5    Z+   08:27   0:00 [test-fork] <defunct>

# 当父进程在休眠书,没有及时回收子进程资源,子进程就变成僵尸进程

2、僵尸进程的销毁

为了销毁子进程,父进程应该主动请求返回子进程的返回值,共有两种方法,分别是wait函数,waitpid函数

2.1 利用wait函数

#include <sys/wait.h>

pid_t wait(int *statloc);
// 成功时返回终止的子进程ID,失败时返回-1

调用该函数后,相关信息保存在statloc指向的地址里。用宏WIFEXITED判断子进程是否正常终止,宏WEXITSTATUS获取子进程的返回值

wait.c

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

int main(int argc,char* argv[]){
    int status;
    pid_t pid = fork();

    if(pid==0){
        return 3;
    }else{
        printf("Child PID: %d \n",pid);
        pid = fork();
        if(pid==0){
            exit(7);
        }else{
            printf("Child PID: %d \n",pid);
            wait(&status);
            if(WIFEXITED(status))
                printf("Child send one: %d \n",WEXITSTATUS(status));

            wait(&status);
            if (WIFEXITED(status))
                printf("Child send one: %d \n", WEXITSTATUS(status));

            sleep(30);
        }
    }
}

运行结果

ubuntu     5053  0.0  0.0   4512   764 pts/5    S+   08:50   0:00 ./bin/wait

# 可知 父进程处于休眠状态时,子进程正常终止

上述代码,共产生两个子进程。父进程利用wait函数对子进程资源进行回收,因此在父进程休眠时,子进程依然可以被正常回收。

注意:当没有子进程终止时,wait函数会阻塞,直到有子进程终止。

2.2 利用waitpid函数

wait函数会引起阻塞,因此可以调用waitpid函数。

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

pid_t waitpid(pid_t pid, int *statloc, int options);

waitpid.c

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

int main(int argc, char* argv[]){
    int status;
    pid_t pid = fork();
    if(pid==0){
        sleep(15);
        return 24;
    }else{
        while(!waitpid(-1, &status,WNOHANG)){
            sleep(3);
            puts("sleep 3sec.");
        }

        if(WIFEXITED(status)){
            printf("Child send %d \n",WEXITSTATUS(status));
        }
    }
    return 0;
}

运行结果

$ gcc waitpid.c -o ./bin/waitpid
$ ./bin/waitpid
sleep 3sec.
sleep 3sec.
sleep 3sec.
sleep 3sec.
sleep 3sec.
Child send 24 

# 输出了5次sleep 3sec.,可知waitpid并未阻塞
$ ps au
ubuntu     8119  0.0  0.0   4512   808 pts/5    S+   09:03   0:00 ./bin/waitpid
ubuntu     8120  0.0  0.0   4380    72 pts/5    S+   09:03   0:00 ./bin/waitpid

# 可知 父进程和子进程都处于休眠状态,之后正常结束

3、信号处理——销毁僵尸进程最常用方法

wait函数和waitpid函数的处理还不够优雅,wait会阻塞父进程,waitpid也需要不断调用来监听子进程结束。接下来介绍信号处理,这是销毁僵尸进程最常用的方法。当子进程结束时,由操作系统向父进程发送一个信号,通知父进程销毁子进程,不管父进程在忙碌中,还是休眠状态。

alarm函数声明

#include <unistd.h>

unsigned int alarm(unsigned int seconds);
// 返回0或以秒为单位的距SIGALRM信号发生所剩时间
// 若传递0,则之前对SIGALRM信号的预约取消

signal函数的声明

#include <signal.h>

void (*signal(int signo, void (*func)(int)))(int);
// 为了在产生信时调用,返回之前注册的函数指针

//函数名 signal
//参数 int signo, void(* func)(int)
//返回类型 : 参数为int型,返回void型函数指针

当子进程结束时,操作系统向父进程发送信号,由父进程销毁子进程。

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

void read_childproc(int sig){
    int status;
    pid_t id = waitpid(-1, &status, WNOHANG); //WNOHANG为
    if(WIFEXITED(status)){
        printf("Remove oroc id: %d\n", id);
        printf("Child send: %d \n",WEXITSTATUS(status));
    }
}

int main(int argc, char* argv[]){
    pid_t pid;
    struct sigaction act;
    
    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGCHLD, &act, 0);
    pid = fork();
    if(pid==0){
        puts("Hi,I'm child process\n");
        sleep(10);
        return 12;
    }else{
        printf("Child proc id : %d\n",pid);
        pid = fork();
        if(pid==0){
            puts("Hi, I'm child process");
            sleep(10);
            exit(24);
        }else{
            printf("Child proc id : %d\n",pid);
            for(int i=0; i<5; i++){
                puts("wait...");
                sleep(5);
            }
        }
    }
    return 0;
}

输出

$ ./bin/remove_zombie 
Child proc id : 881
Child proc id : 883
wait...
Hi, I'm child process
Hi,I'm child process
wait...
wait...
Remove oroc id: 883
Child send: 24 
wait...
Remove oroc id: 881
Child send: 12 
wait...
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 中,僵尸进程和孤儿进程都是指与父进程不再有联系的进程,它们通常是由于进程管理不当或程序错误导致的。 **僵尸进程**是已经完成执行任务,但其父进程还没有来得及处理其退出状态的进程。当进程完成执行后,它的退出状态(也称为退出码或终止状态)会被保存在系统中,直到父进程通过 `wait` 或 `waitpid` 等函数来获取该状态。如果父进程没有处理该状态,那么该进程就会成为僵尸进程,占用系统资源。要清理僵尸进程,可以使用 `kill` 命令向其父进程发送 `SIGCHLD` 信号,或者重新编写程序,使其正确处理子进程的退出状态。 **孤儿进程**是指其父进程已经退出或被终止,但其自身仍在运行的进程。孤儿进程会被 `init` 进程(进程号为 `1`)接管,`init` 进程会定期检查系统中是否有孤儿进程,并且将其的父进程设置为 `init` 进程。要避免孤儿进程的产生,可以在父进程退出之前,等待子进程的退出,或者将子进程的父进程设置为 `init` 进程。 可以使用 `ps` 命令来查看系统中的僵尸进程和孤儿进程。使用以下命令可以查看所有僵尸进程: ``` ps aux | grep 'Z' ``` 其中,`aux` 选项用于显示所有进程,`grep 'Z'` 用于查找所有状态为 `Z` 的进程,即僵尸进程。 使用以下命令可以查看所有孤儿进程: ``` ps -ejH ``` 其中,`-e` 选项用于显示所有进程,`-j` 选项用于以层次结构的形式显示进程,`-H` 选项用于显示所有孤儿进程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值