Linux 系统中僵尸进程

嵌入式技术

1372人已加入

描述


Linux 系统中僵尸进程和现实中僵尸(虽然我也没见过)类似,虽然已经死了,但是由于没人给它们收尸,还能四处走动。僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸。

僵尸进程如何产生的?

如果一个进程在其终止的时候,自己就回收所有分配给它的资源,系统就不会产生所谓的僵尸进程了。那么我们说一个进程终止之后,还保留哪些信息?为什么终止之后还需要保留这些信息呢?

一个进程终止的方法很多,进程终止后有些信息对于父进程和内核还是很有用的,例如进程的ID号、进程的退出状态、进程运行的CPU时间等。因此进程在终止时,回收所有内核分配给它的内存、关闭它打开的所有文件等等,但是还会保留以上极少的信息,以供父进程使用。父进程可以使用 wait/waitpid 等系统调用来为子进程收拾,做一些收尾工作。

因此,一个僵尸进程产生的过程是:父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD信号,父进程此时应该调用wait()系统调用来获取子进程的退出状态以及其它的信息。在 wait 调用之后,僵尸进程就完全从内存中移除。因此一个僵尸存在于其终止到父进程调用 wait 等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。

在 Linux 下,我们可以使用 ps 等命令查看系统中僵尸进程,僵尸进程的状态标记为‘Z’:

产生一个僵尸进程

根据上面的描述,我们很容易去写一个程序来产生僵尸进程,如下代码:

 
 
 
 
 
 
 
#include #include int main(){ //fork a child process pid_t pid = fork(); if (pid > 0) //parent process { printf("in parent process, sleep for one miniute...zZ...n"); sleep(60); printf("after sleeping, and exit!n"); } else if (pid == 0) { //child process exit, and to be a zombie process printf("in child process, and exit!n"); exit(0); } return 0;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include
#include
 
int main()
{
    //fork a child process
    pid_t pid = fork();
 
    if (pid > 0)   //parent process
    {
        printf("in parent process, sleep for one miniute...zZ...n");
        sleep(60);
        printf("after sleeping, and exit!n");
    }
    else if (pid == 0)  
    {
        //child process exit, and to be a zombie process
        printf("in child process, and exit!n");
        exit(0);
    }
 
    return 0;
}

父进程并没有写 wait 等系统调用函数,因此在子进程退出之后变成僵尸进程,父进程并没有为其去收尸。我们使用下面命令编译运行该进程,然后查看系统中进程状态:

 

 
 
 
 
 
 
Shell
 
guohailin@guohailin:~/Documents$ gcc zombie.c -o zombieguohailin@guohailin:~/Documents$ ./zombiein parent process, sleep for one miniute...zZ...in child process, and exit!# 打开另一个终端:guohailin@guohailin:~$ ps aux | grep -w 'Z'1000 2211 1.2 0.0 0 0 ? Z 13:24 6:53 [chromium-browse] 1000 4400 0.0 0.0 0 0 ? Z 10月16 0:00 [fcitx] 1000 10871 0.0 0.0 0 0 pts/4 Z+ 22:32 0:00 [zombie]
1
2
3
4
5
6
7
8
9
10
guohailin@guohailin:~/Documents$ gcc zombie.c -o zombie
guohailin@guohailin:~/Documents$ ./zombie
in parent process, sleep for one miniute...zZ...
in child process, and exit!
 
# 打开另一个终端:
guohailin@guohailin:~$ ps aux | grep -w 'Z'
1000      2211  1.2  0.0      0     0 ?        Z    13:24   6:53 [chromium-browse]
1000      4400  0.0  0.0      0     0 ?        Z    1016   0:00 [fcitx]
1000     10871  0.0  0.0      0     0 pts/4    Z+   22:32   0:00 [zombie]

 

从上面可以看出,系统中多了一个僵尸进程。但如果等父进程睡眠醒来退出之后,我们再次查看系统进程信息,发现刚才的僵尸进程不见了。

 

 
 
 
 
 
 
Shell
 
guohailin@guohailin:~/Documents$ ./zombie in parent process, sleep for one miniute...zZ...in child process, and exit!after sleeping, and exit!guohailin@guohailin:~/Documents$ ps aux | grep -w 'Z'1000 2211 1.2 0.0 0 0 ? Z 13:24 6:53 [chromium-browse] 1000 4400 0.0 0.0 0 0 ? Z 10月16 0:00 [fcitx]
1
2
3
4
5
6
7
guohailin@guohailin:~/Documents$ ./zombie
in parent process, sleep for one miniute...zZ...
in child process, and exit!
after sleeping, and exit!
guohailin@guohailin:~/Documents$ ps aux | grep -w 'Z'
1000 2211 1.2 0.0 0 0 ? Z 13:24 6:53 [chromium-browse]
1000 4400 0.0 0.0 0 0 ? Z 1016 0:00 [fcitx]

 

这是为什么呢?父进程到死都也没有为其子进程收尸呀,怎么父进程退出之后,那个僵尸进程就消失了呢?难道父进程在退出时会为子进程收拾吗?其实不然….真正的原因是:父进程死掉之后,其所有子进程过继给 init 进程,init 进程成为该僵尸进程的新进程,init 进程会周期性地去调用 wait 系统调用来清除它的僵尸孩子。因此,你会发现上面例子中父进程死掉之后,僵尸进程也跟着消失,其实是 init 进程为其收尸的!

怎样避免僵尸进程的产生

不能使用 kill 后接SIGKILL信号这样的命令像杀死普通进程一样杀死僵尸进程,因为僵尸进程是已经死掉的进程,它不能再接收任何信号。事实上,如果系统中僵尸进程并不多的话,我们也无需去消除它们,少数的僵尸进程并不会对系统的性能有什么影响。

那么在编程时,如果能避免系统中大量产生僵尸进程呢?根据上面描述的,子进程在终止时会向父进程发 SIGCHLD 信号,Linux 默认是忽略该信号的,我们可以显示安装该信号,在信号处理函数中调用 wait 等函数来为其收尸,这样就能避免僵尸进程长期存在于系统中了。示例代码如下:

 
 
 
 
 
 
 
#include #include #include #include #include sig_atomic_t child_exit_status;void clean_up_child_process(int signal_num){ /* clean up child process */ int status; wait (&status); /* store its exit status in a global variable */ child_exit_status = status;}int main(){ /* handle SIGCHLD by calling clean_up_child_process */ struct sigaction sigchild_action; memset(&sigchild_action, 0, sizeof(sigchild_action)); sigchild_action.sa_handler = &clean_up_child_process; sigaction(SIGCHLD, &sigchild_action, NULL); /* fork a child, and let the child process dies before parent */ pid_t c_pid; c_pid = fork(); if (c_pid > 0) { printf("in parent process, and sleep for on mininute...zZ...n"); sleep(60); } else if(c_pid == 0) { printf("in child process, and exit nown"); exit(0); } else { printf("fork failed!n"); } return 0;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include
#include
#include
#include
#include
 
sig_atomic_t child_exit_status;
 
void clean_up_child_process(int signal_num)
{
    /* clean up child process */
    int status;
    wait (&status);
 
    /* store its exit status in a global variable */
    child_exit_status = status;
}
 
int main()
{
    /* handle SIGCHLD by calling clean_up_child_process  */
    struct sigaction sigchild_action;
    memset(&sigchild_action, 0, sizeof(sigchild_action));
    sigchild_action.sa_handler = &clean_up_child_process;
    sigaction(SIGCHLD, &sigchild_action, NULL);
 
    /* fork a child, and let the child process dies before parent */
    pid_t c_pid;
    c_pid = fork();
    if (c_pid > 0)
    {
        printf("in parent process, and sleep for on mininute...zZ...n");
        sleep(60);
    }
    else if(c_pid == 0)
    {
        printf("in child process, and exit nown");
        exit(0);
    }
    else
    {
        printf("fork failed!n");
    }
 
    return 0;
}  

 

参考资料
  • http://en.wikipedia.org/wiki/Zombie_process
  • http://simplestcodings.blogspot.com/2010/10/story-of-zombie-process.html
  • http://www.howtogeek.com/119815/



打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 相关推荐

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分