Linux 系统中僵尸进程

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

僵尸进程如何产生的?

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

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

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

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

https://dn-linuxcn.qbox.me/data/attachment/album/201404/09/145023nsfw6cj420uo6ofo.png

产生一个僵尸进程

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

#include 
#include <sys/types.h>

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

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    10月16   0:00 [fcitx] 
1000     10871  0.0  0.0      0     0 pts/4    Z+   22:32   0:00 [zombie]

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

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    10月16   0:00 [fcitx] 
1000     10871  0.0  0.0      0     0 pts/4    Z+   22:32   0:00 [zombie]

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

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]

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

怎样避免僵尸进程的产生

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

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

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

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 now\n");
        exit(0);
    }
    else
    {
        printf("fork failed!\n");
    }

    return 0;
}   

参考资料

  原文发布时间为:2013-10-20

时间: 2024-11-01 03:56:04

Linux 系统中僵尸进程的相关文章

Linux系统中的进程管理简介

在Linux系统里,当前正在运行的程序实例称为进程.比如,当你启动Apache的时候,系统会为它分配一个进程ID.然后就可以用这个ID监视和控制这个程序. 进程监视和控制是任何Linux系统管理员的核心任务.一个管理员可以终止("kill").重启一个进程,甚至可以为它指定一个不同的优先级.标准的Linux命令"ps"和"top"通常用于查看当前的进程列表.下面我来说明如何用这些命令和其它命令来管理Linux系统中的进程. 用ps监视进程 一个监

在Linux系统中查看进程及杀死僵尸进程的方法

  ps 命令 ps 命令就是最根本相应情况下也是相当强大地进程查看命令.运用该命令可以确定有哪些进程正在运行和运行地状态. 进程 是否结束.进程有没有僵死.哪些进程占用了过多地资源等等.总之大部分信息均为可以通过执行该命令得到地. ps 命令及其参数 ps 命令最经常使用地还是用于监控后台进程地工作情况,因为后台进程是不和屏幕键盘这些标准输入/输出设 备进行通信地,所以如果需要检测其情况,便可以运用 ps 命令了. 该命令语法格式如下: ps [选项] -e 显示所有进程,环境变量 -f 全格

详解Linux系统中的进程初始化配置文件inittab

  /etc/inittab文件描述了哪些进程开始启动并正常运行.inittab为linux初始化文件系统时init初始化程序用到的配置文件.这个文件负责设置init初始化程序初始化脚本在哪里;每个运行级初始化时运行的命令; 开机.关机.重启对应的命令;各运行级登陆时所运行的命令. inittab文件中的条目的格式如下: label:runlevels:action:process 下面分别介绍各个字段 1.label label 是一个独特的序列,确定了一个条目1 - 4字符,一些系统只支持2

Linux系统中的进程管理工具SystemD介绍

  SystemD是Linux下的一种init软件,由Lennart Poettering带头开发,并在LGPL 2.1及其后续版本许可证下开源发布.Lennart是redhat员工,但SystemD不是redhat项目.其开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell的系统开销的效果,最终代替现在常用的System V与BSD风格init程序. SystemD这一名字源于Unix中的一个惯例:在Unix中常以"d"作

Linux系统编程----僵尸进程

什么是僵尸进程? 僵尸进程, 指子进程退出后, 父进程还没有回收子进程的资源,这个子进程就处在于僵尸状态. 来看看如何产生? #include <stdio.h> #include <unistd.h> int main(void) { pid_t pid ; //zombie 子进程先退出,父进程没有回收子进程资源 //这就是僵尸进程 //僵尸进程不能被直接杀死,只能通过杀死父进程来终止僵尸进程 pid = fork(); if(pid == 0) { printf("

Linux 系统应用编程——进程基础

一.Linux下多任务机制的介绍          Linux有一特性是多任务,多任务处理是指用户可以在同一时间内运行多个应用程序,每个正在执行的应用程序被称为一个任务.          多任务操作系统使用某种调度(shedule)策略(由内核来执行)支持多个任务并发执行.事实上,(单核)处理器在某一时刻只能执行一个任务.每个任务创建时被分配时间片(几十到上百毫秒),任务执行(占用CPU)时,时间片递减.操作系统会在当前任务的时间片用完时调度执行其他任务.由于任务会频繁地切换执行,因此给用户多

Linux下杀僵尸进程办法

1) 检查当前僵尸进程信息 # ps -ef | grep defunct | grep -v grep | wc -l 175 # top | head -2 top - 15:05:54 up 97 days, 23:49, 4 users, load average: 0.66, 0.45, 0.39 Tasks: 829 total, 1 running, 479 sleeping, 174 stopped, 175 zombie # ps -ef | grep defunct | gr

深入解析Linux系统中的SELinux访问控制功能

  SELinux(Security-Enhanced Linux) 是美国国家安全局(NSA)对于强制访问控制的实现,是 Linux历史上最杰出的新安全子系统.NSA是在Linux社区的帮助下开发了一种访问控制体系,在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件.SELinux 默认安装在 Fedora 和 Red Hat Enterprise Linux 上,也可以作为其他发行版上容易安装的包得到. SELinux 是 2.6 版本的 Linux 内核中提供的强制访问控制

详解Linux系统中的root权限控制

  在Linux操作系统中,root的权限是最高的,也被称为超级权限的拥有者.普通用户无法执行的操作,root用户都能完成,所以也被称之为超级管理用户. 在系统中,每个文件.目录和进程,都归属于某一个用户,没有用户许可其它普通用户是无法操作的,但对root除外.root用户的特权性还表现在root 可以超越任何用户和用户组来对文件或目录进行读取.修改或删除(在系统正常的许可范围内);对可执行程序的执行.终止;对硬件设备的添加.创建和移除等; 也可以对文件和目录进行属主和权限进行修改,以适合系统管