僵尸进程问题排查方法

来源:互联网 发布:林珊珊的淘宝店质量 编辑:程序博客网 时间:2024/06/03 19:53

项目里遇到了一个问题,自己负责的模块产生在长时间挂机后,会产生许多僵尸进程,一段时间后,会耗费所有的资源,导致设备无法进行别的操作,设备挂死。
排查如下:

  1. 首先需要找到这些僵尸进程的父进程,因为我们使用的是嵌入式设备,所以许多命令都是busybox提供,许多功能都经过了阉割,比如传统的ps,许多参数是不能携带的,所以无法直接看出这些僵尸进程的父进程,我们可以通过查看/proc/进程号/status文件,此文件里能反馈大部分信息。通过此方式,我找到了所有僵尸进程的父进程。当然,还有个蠢办法,就是kill掉你怀疑的进程,如果该进程的死亡,导致所有僵尸进程也消亡,那么恭喜你,你get到了。
  2. 通过上面的方法,悲剧地发现,导致僵尸进程的进程,正是我负责的模块。查阅资料,僵尸进程产生无非就是父进程没有等待子进程,子进程结束后资源得不到完全释放。所以主要有以下两个方面需要排查。

    • 显式fork:这种比较好排查,原因是fork子进程后,父进程有没有调用wait或者waitpid来等待子进程,而往往父进程会忙于其他事宜,不会阻塞等待子进程,这时候就需要信号来软中断父进程来调用wait或者waitpid
    • 隐式fork:这种往往是调用一些能够产生子进程的系统函数,比如popen函数,popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。这个进程必须由 pclose() 函数关闭,而不是
      fclose()
      函数,如果没有调用pclose()或者勿用fclose(),就会导致隐式fork出来的子进程无法正常关闭,从而也会导致僵尸进程。

分析了原因,果然是代码模块里,使用了fclose()来关闭popen()打开的文件描述符,导致僵尸进程的产生。修改后解决。
但是往往有些时候,一些进程问题并不好定位,所以可能有些人也为了图省事,直接在主进程里注册一个信号处理函数,用来接受处理子进程结束的信号,这种方法未尝不可,只是有时候带来了一些意想不到的效果。比如如下代码

#include <stdio.h>#include <unistd.h>#include <errno.h>#include <stdlib.h>#include <signal.h>static void sig_child(int signo){     pid_t        pid;     int        stat;     //处理僵尸进程     while ((pid = waitpid(-1, &stat, WNOHANG)) >0)            printf("child %d terminated.\n", pid);}int main(){    pid_t pid;    pthread_t ntid;    int i=0;    //创建捕捉子进程退出信号    signal(SIGCHLD,sig_child);    pid = fork();    if (pid < 0)    {        perror("fork error:");        exit(1);    }    else if (pid == 0)    {        printf("I am child process,pid id %d.I am exiting.\n",getpid());        exit(0);    }    printf("I am father process.I will sleep 100 seconds\n");    //等待子进程先退出    sleep(30);    //输出进程信息    //system("ps -o pid,ppid,state,tty,command");    printf("father process is exiting.\n");    return 0;}

上述代码本意是父进程sleep 30s,子进程执行打印后退出,那么,注册函数sig_child()会调用waitpid()来回收子进程,函数继续sleep 30s后打印出“father process is exiting. ”。但是事与愿违,你会发现,执行的时候,“father process is exiting. ”会马上就打印出来,根本没有在30s后执行最后的打印。这个还得从信号的原理出发,信号会唤醒可中断休眠进程,也就是子进程发送子进程结束信号SIGCHLD给父进程,而父进程已经休眠,此时信号会唤醒休眠的父进程,导致sleep失效。聪敏的你可能会说,那我不用sleep来睡眠,可以使用select来实现等待,这个确实可以,但是对于大型程序,难免里面会用到sleep,所以这个也不是最好的方法。

我们还有一种选择,就是使用signal(SIGCHLD,SIG_IGN),在这种方式下,子进程状态信息会被丢弃,也就是自动回收了,所以不会产生僵尸进程,但是问题也就来了,wait,waitpid却无法捕捉到子进程状态信息了,如果你随后调用了wait,那么会阻塞到所有的子进程结束,并返回错误ECHILD,也就是没有子进程等待。如果父进程确实不关心子进程,那么这个就是好的方法。

所以,具体问题具体对待,没有最好的,只有合适的。

原创粉丝点击