Linux-僵尸进程产生与处理

目录

背景

产生

处理方法 

方法一:父进程通过wait或者wait_pid方式回收子进程

方法二:信号处理signal

改进版


背景

        父进程创建子进程之后,父进程没有等待该子进程的退出,子进程就会成为僵尸进程,如果父进程也退出,这个时候子进程也可以被init进程回收,释放资源。如果父进程不退出,子进程占用的资源将永远不会被释放。

产生

以下示例是一个产生僵尸进程的典型例子

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


int main(int argc, char **argv) {

    pid_t pid = fork();

    if(pid > 0){
        printf("i am parent process PID=%d\n", getpid());
        sleep(10);
    }else if(pid == 0) {
        printf("i am child process PID=%d\n", getpid());
        return 0;
    }else{
        printf("fork error\n");
        return -1;
    }
    return 0;

}	

运行结果:

$ ./test
i am parent process PID=1742
i am child process PID=1743

查看进程:

$ ps -ef | grep test
pi 1742 1040 0 19:19 pts/0 00:00:00 ./test
pi 1743 1742 0 19:19 pts/0 00:00:00 [test] <defunct>
pi 1746 1040 0 19:19 pts/0 00:00:00 grep --color=auto test

当主进程十秒运行结束之后

[1]+ 已完成 ./test
$ ps -ef | grep test
pi 1750 1040 0 19:19 pts/0 00:00:00 grep --color=auto test

        可以看到有“已完成”字样的打印,但是再次查看进程列表的时候发现,僵尸进程[test] <default>已经不存在了,从以上的打印可以看到,开始主进程运行进行sleep等待,子进程运行结束之后立马退出,产生僵尸进程;等到父进程1742结束之后,僵尸进程1743被init进程回收。

处理方法 

        那么如何消除这种子进程退出后成为僵尸进程的问题呢?

方法一:父进程通过wait或者wait_pid方式回收子进程

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


int main(int argc, char **argv) {

    pid_t pid = fork();
    int status, i;

    if(pid > 0){
        printf("i am parent process PID=%d\n", getpid());
        //wait(NULL);
        wait(&status);
        i = WEXITSTATUS(status);
        printf("child process done! status=%d\n", i);
       //sleep(10);
    }else if(pid == 0) {
        printf("i am child process PID=%d\n", getpid());
        sleep(5);
        return 0;
    }else{
        printf("fork error\n");
        return -1;
    }
    return 0;

}

运行结果

i am parent process PID=1841
i am child process PID=1842
child process done! status=0

        可以看到产生子进程之后打印 printf("child process done!\n");被wait阻塞,说明wait是阻塞型的,同样我们通过ps -ef查看进程列表,可以发现没有出现僵尸进程。当我们不关心子进程状态时候,这里wait的参数可以设定为NULL。

        watipid(pid_t pid, int *status, int options)是在wait(int *status)的基础上增加了一些个性化设定,可以监听一组、或者指定的子进程pid,具体使用如下:

参数pid
pid<-1 等待进程组识别码为pid绝对值的任何子进程。
pid=-1 等待任何子进程,相当于wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为pid的子进程。

参数options
参数option可以为0 或下面的OR 组合
WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以等待。
WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。

参数status返回值
WIFEXITED(status)如果子进程正常结束则为非0值。
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。

waitpid返回值
如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中

        上面这个会有一个问题,调用wait的父进程会被一直阻塞,它无法继续执行后面的任务,直到子进程退出之后。那么针对这种情况如何处理呢?可以将wait用信号来代替

方法二:信号处理signal

void (*signal(int signum,void(* handler)(int)))(int);

函数说明:
signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针,则必须是下列两个常数之一:
SIG_IGN 忽略参数signum指定的信号。
SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式。

        子进程退出的时候内核会发送SIGCHLD给父进程,所以父进程可以监听这个信号,并设定信号处理函数,上面的示例可以改进如下:

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

void childProcess(){
    int round = 10;

    while(round > 0){
        printf("round=%d\n", round);
        sleep(1);
        round--;
    }
}

void sig_handler(int signum){
    pid_t pid;
    if(signum == SIGCHLD){
        printf("get child exit signal\n");
        while((pid = waitpid(-1, NULL, WNOHANG)) > 0){
            printf("child %d exit\n", pid);
        }
    }
}

int main(int argc, char **argv) {

    pid_t pid = fork();
    //int status, i;
    signal(SIGCHLD, sig_handler);
    int tmp = 10;

    if(pid > 0){
        printf("i am parent process PID=%d\n", getpid());
        //wait(NULL);
        //wait(&status);
        //i = WEXITSTATUS(status);
        //printf("child process done! status=%d\n", i);
        sleep(5);
        printf("do parent`s thing\n");
        while(tmp > 0){
            sleep(1);
            printf("parent loop =%d\n", tmp);
            tmp--;
        }
        printf("parent exit\n");
        return 0;
    }else if(pid == 0) {
        printf("i am child process PID=%d\n", getpid());
        //sleep(5);
        childProcess();
        return 0;
    }else{
        printf("fork error\n");
        return -1;
    }
    return 0;

}

运行效果

./test
i am parent process PID=2014
i am child process PID=2015
round=10
round=9
round=8
round=7
round=6
do parent`s thing
round=5
parent loop =10
round=4
parent loop =9
round=3
parent loop =8
round=2
parent loop =7
round=1
parent loop =6
get child exit signal
child 2015 exit
parent loop =5
parent loop =4
parent loop =3
parent loop =2
parent loop =1
parent exit

        这里需要注意,我们用的是waitpid方式来处理子进程回收的监听,如果系统繁忙时,有两个子进程同时结束,这时只会发送一个SIGCHLD信号,如果只wait一次,也会产生僵尸进程,所以这里使用waitpid加带入WNOHANG参数,使得调用的时候立马返回,根据返回值判断等待结果。

改进版

当然比较简单的应用,我们可以直接用wait(NULL)的方式,它等价于waitpid(-1, NULL, 0),具体可以看上面关于waitpid参数说明。

当然我们也可以给signal的第二个参数直接输入一个常数,也可以达到回收效果

signal(SIGCHLD, SIG_IGN);

以上表示直接忽略SIGCHLD信号,让父进程不必关心子进程退出状态,直接清理。

如果参数handler不是函数指针,则必须是下列两个常数之一:
SIG_IGN 忽略参数signum指定的信号。
SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式。

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

void childProcess(){
    int round = 10;

    while(round > 0){
        printf("round=%d\n", round);
        sleep(1);
        round--;
    }
}

void sig_handler(int signum){
    pid_t pid;
    if(signum == SIGCHLD){
        printf("get child exit signal\n");
        while((pid = waitpid(-1, NULL, WNOHANG)) > 0){
            printf("child %d exit\n", pid);
        }
    }
}

int main(int argc, char **argv) {

    pid_t pid = fork();
    //int status, i;
    //signal(SIGCHLD, sig_handler);
    signal(SIGCHLD, SIG_IGN);
    int tmp = 10;

    if(pid > 0){
        printf("i am parent process PID=%d\n", getpid());
        //wait(NULL);
        //wait(&status);
        //i = WEXITSTATUS(status);
        //printf("child process done! status=%d\n", i);
        sleep(5);
        printf("do parent`s thing\n");
        while(tmp > 0){
            sleep(1);
            printf("parent loop =%d\n", tmp);
            tmp--;
        }
        printf("parent exit\n");
        return 0;
    }else if(pid == 0) {
        printf("i am child process PID=%d\n", getpid());
        //sleep(5);
        childProcess();
        return 0;
    }else{
        printf("fork error\n");
        return -1;
    }
    return 0;

}

i am parent process PID=2047
i am child process PID=2048
round=10
round=9
round=8
round=7
round=6
do parent`s thing
round=5
parent loop =10
round=4
parent loop =9
round=3
parent loop =8
round=2
parent loop =7
round=1
parent loop =6
parent loop =5
parent loop =4
parent loop =3
parent loop =2
parent loop =1
parent exit

从打印看,子进程退出之后,父进程并没有做特殊处理,直接回收(可以在子进程退出执行完之后,ps查看是否有对应子进程pid)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值