CentOS下清除僵尸进程

2018-12-06 21:30:00    赵勤松    2124    原创
摘要: 在使用C语言进行多进程开发时,会出现僵尸进程,本文将分析僵尸进程的产生原因,以及如何消除它

Linux系统中,系统提供一种机制,可以让父进程得以了解子进程的当前状态,当一个进程结束时,内核会释放该进程申请的所有资源,如占用的内存,打开的文件等,但进程自身的一些注册信息仍会被保留,如进程ID,退出状态,运行时间等等,当父进程使用wait或waitpid获取了子进程状态时,这些信息才会被完全释放掉,而直到这时,该进程的信息才被完全清除掉。进程退出,但其注册信息未被父进程获取时,我们就称为僵尸状态,其进程也变成了僵尸进程。

僵尸进程对系统的危害,不仅仅是一部分内存无法释放,要知道,Linux系统是限制打开的总进程数的,大量僵尸进程不断吞噬可用进程空间,最终系统将无法运行新进程,因此,如何防止僵尸进程的出现,是我们必须要了解的。

在此需要先说明一下Linux系统中init进程的作用,当一个父进程先于子进程退出时,子进程的信息将会被转交到init进程下做统一管理,init进程的作用,相当于就是不断地获取子进程的注册信息,以完成清理注册信息的目的,这个进程在我们清理僵尸进程的关键。

针对僵尸进程的出现条件,我们使用C语言进行场景出现。


zombie1.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
    	perror("fork error!\n");
        exit(1);
    }
    else if (pid == 0)
    {
    	exit(0);
    }
    sleep(10);
    exit(0);
}

将此文件进行编绎并在后台执行,通过查询进程状态,我们来观察僵尸进程的存在


# gcc -o zombie1 zombie1.c # ./zombie1 & # ps aux | grep zombie1 (上一命令结束后立即执行) root 1383 0.0 0.0 4164 344 pts/0 S 17:22 0:00 ./zombie1 root 1384 0.0 0.0 0 0 pts/0 Z 17:22 0:00 [zombie1] <defunct> root 1386 0.0 0.0 112660 964 pts/0 S+ 17:22 0:00 grep --color=auto zombie1 # ps aux | grep zombie1 (等待10秒以后执行) root 1388 0.0 0.0 112660 964 pts/0 S+ 17:22 0:00 grep --color=auto zombie1 [1]+ Done ./zombie1

从以上执行结果可以看出,第一次获取进程状态时,因为子进程已经结束,而父进程尚未执行wait操作,导致子进程的注册信息没有清除,因此该子进程显示为僵尸进行(状态为Z),第二次获取进程状态时,父进程已经执行了wait操作,子进程的信息已经消失,不再做为僵尸进程存在。

在我们进程开发过程中,可能会出现父进程长时间不消亡,而子进程已结束的情况,这时,避免子进程成为僵尸进程,一般有2种办法。


1.应用SIGCHILD信号进行处理

zombie2.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
static void clear_child(int);
int main(void)
{
    pid_t pid;
    signal(SIGCHLD, clear_child);
    pid = fork();
    if (pid < 0)
    {
        perror("fork error!\n");
        exit(1);
    }
    else if (pid == 0)
    {
        exit(0);
    }
    sleep(2);  // 当子进程消亡时,SIGCHLD信号将唤醒父进程,从而结束此sleep
    sleep(10);
    exit(0);
}
static void clear_child(int signo)
{
    wait(NULL);
}

将此文件进行编绎并在后台执行,通过查询进程状态,我们来观察僵尸进程的存在


# gcc -o zombie2 zombie2.c # ./zombie2 & # ps aux | grep zombie2 (上一命令结束后立即执行) root      1839  0.0  0.0   4164   348 pts/0    S    19:14   0:00 ./zombie2 root      1842  0.0  0.0 112660   964 pts/0    R+   19:14   0:00 grep --color=auto zombie2 # ps aux | grep zombie2 (等待12秒以后执行) root      1844  0.0  0.0 112660   964 pts/0    S+   19:14   0:00 grep --color=auto zombie2 [1]+ Done ./zombie2

从以上执行结果可以看出,第一次获取进程状态时,因为子进程已经结束,而父进程因为SIGCHILD信号触发的缘故,从2秒休眠中唤醒,执行了wait操作,因此僵尸进程已经清除,只余下主进程。第二次获取进程状态时,父进程的第2个10秒休眠已经结束,全部相关进程已经清除。


2.fork两次,消除僵尸进程

zombie3.c #include <stdio.h> #include <stdlib.h> int main(void) {     pid_t pid;     pid = fork();     if (pid < 0)     {         perror("fork error!\n");         exit(1);     }     else if (pid == 0)     {         pid = fork();         if (pid < 0)         {             perror("fork error!\n");             exit(1);         }         else if (pid == 0)         {     sleep(5);     exit(0);         }         exit(0);     }     waitpid(pid, NULL, 0);     sleep(10); // 睡眠期间,两个子进程均已消亡     exit(0); }

将此文件进行编绎并在后台执行,通过查询进程状态,我们来观察僵尸进程的存在


# gcc -o zombie3 zombie3.c # ./zombie3 & # ps aux | grep zombie3 (上一命令结束后立即执行) root      1861  0.0  0.0   4164   344 pts/0    S    19:24   0:00 ./zombie3 root      1863  0.0  0.0   4164    84 pts/0    S    19:24   0:00 ./zombie3 root      1865  0.0  0.0 112660   968 pts/0    R+   19:24   0:00 grep --color=auto zombie3 # ps aux | grep zombie3 (等待7秒以后执行) root      1861  0.0  0.0   4164   344 pts/0    S    19:24   0:00 ./zombie3 root      1869  0.0  0.0 112660   968 pts/0    R+   19:24   0:00 grep --color=auto zombie3 # ps aux | grep zombie3 (等待10秒以后执行)   root      1875  0.0  0.0 112660   964 pts/0    R+   19:24   0:00 grep --color=auto zombie3 [1]+ Done ./zombie3

从以上执行结果可以看出,第一次获取进程状态时,因为子进程已经结束,而父进程也执行了wait操作,因此子进程已经清除不可见,只剩下父进程和孙进程,第二次获取进程状态时,孙进程已经消亡,只剩下了父进程。第三次获取进程状态时,全部相关进程都已经清除。

京ICP备15015023号-1
蝉知7.4.1