linux处理僵尸进程

由来

在linux下,如果一个进程终止,内核会释放该进程使用的所有存储区,关闭所有文件句柄等,但是,内核会为每个终止子进程保留一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间。当终止子进程的父进程调用wait或waitpid时就可以得到这些信息

僵尸进程指:一个进程退出后,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程

任何一个子进程(init除外)在退出后并非马上就消失,而是留下一个称为僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号

作用

设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。init进程将清理它们(也就是说init进程将wait它们,从而去除它们的僵尸状态)

危害

内核保存僵尸进程信息,这就是占用了系统资源,而且僵尸进程的进程号一直会被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程

避免

1. 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。

2. 父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出,wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。

3. 如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出

4. 通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。

第一种方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。

wait函数

pid_t wait(int *status);

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。 
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

  • wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
  • 返回的是子进程的PID,它通常是结束的子进程
  • 状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
  • 如果status不是一个空指针,状态信息将被写入它指向的位置

可以上述的一些宏判断子进程的退出情况:

waitpid函数

pid_t waitpid(pid_t pid, int *status, int options);

status:如果不是空,会把状态信息写到它指向的位置,与wait一样

options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起

The value of options is an OR of zero or more  of  the  following  con- 
stants:

WNOHANG     return immediately if no child has exited.

WUNTRACED   also  return  if  a  child  has stopped (but not traced via 
            ptrace(2)).  Status for traced children which have  stopped 
            is provided even if this option is not specified.

WCONTINUED (since Linux 2.6.10) 
            also return if a stopped child has been resumed by delivery 
            of SIGCONT.

返回值:如果成功返回等待子进程的ID,失败返回-1

对于waitpid的p i d参数的解释与其值有关:

pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。

pid > 0 等待其进程I D与p i d相等的子进程。

pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。

pid < -1 等待其组I D等于p i d的绝对值的任一子进程

wait与waitpid区别:

  • 在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
  • waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
  • 实际上wait函数是waitpid函数的一个特例。waitpid(-1, &status, 0);

如果使用wait处理僵尸进程,如下程序:

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

int cnt = 0;

void waitpidexit(int sig)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
    {
        cnt++;
    }
}

void waitexit(int sig)
{
    wait(NULL);
    cnt++;
}

int main()
{
    int pid;

    printf("%d\n", getpid());

    // signal(SIGCHLD, SIG_IGN);
    signal(SIGCHLD, waitexit);
    // signal(SIGCHLD, waitpidexit);

    for (int i = 0; i < 500; ++i)
    {
        pid = fork();
        if (pid == 0 || pid < 0)
        {
            break;
        }
    }

    if (pid == 0)
    {
        exit(0);
    }
    else if (pid < 0)
    {
        fprintf(stderr, "fork error: %d, %s\n", errno, strerror(errno));
        exit(0);
    }
    else
    {
        getchar();

        printf("%d\n", cnt);
    }

    return 0;
}

运行之后,发现还是有很多僵尸进程:

这是因为 SIGCHLD 为不可靠非实时信号,不支持排队,可能会造成信号丢失。假如在某一时间段多个子进程退出后都会发出SIGCHLD信号,但父进程来不及一个一个地响应,所以最后父进程实际上只执行了一次信号处理函数。但执行一次信号处理函数只等待一个子进程退出,所以最后会有一些子进程依然是僵尸进程。

虽然这样但是有一点是明了的,就是收到SIGCHLD必然有子进程退出,而我们可以在信号处理函数里循环调用waitpid函数来等待所有的退出的子进程。至于为什么不用wait,主要原因是在wait在清理完所有僵尸进程后再次等待会阻塞。

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

int cnt = 0;

void waitpidexit(int sig)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
    {
        cnt++;
    }
}

void waitexit(int sig)
{
    wait(NULL);
    cnt++;
}

int main()
{
    int pid;

    printf("%d\n", getpid());

    // signal(SIGCHLD, SIG_IGN);
    // signal(SIGCHLD, waitexit);
    signal(SIGCHLD, waitpidexit);

    for (int i = 0; i < 500; ++i)
    {
        pid = fork();
        if (pid == 0 || pid < 0)
        {
            break;
        }
    }

    if (pid == 0)
    {
        exit(0);
    }
    else if (pid < 0)
    {
        fprintf(stderr, "fork error: %d, %s\n", errno, strerror(errno));
        exit(0);
    }
    else
    {
        getchar();

        printf("%d\n", cnt);
    }

    return 0;
}

原文地址:https://www.cnblogs.com/zuofaqi/p/9966685.html

时间: 2024-10-03 10:53:49

linux处理僵尸进程的相关文章

linux的僵尸进程和孤儿进程

1 僵尸进程: 子进程已经退出勒 但是还没有回收资源的进程为僵尸进程 代码验证 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 5 int main() 6 { 7 pid_t pid; 8 if((pid=fork())==-1) 9 { 10 perror("fork error"); 11 12 }else if(pid==0){ 13 printf(&quo

Linux中的僵尸进程和孤儿进程

在UNIX里,除了进程0(即PID=0的交换进程,Swapper Process)以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程.        操作系统内核以进程标识符(Process Identifier,即PID)来识别进程.进程0是系统引导时创建的一个特殊进程,在其调用fork创建出一个子进程(即PID=1的进程1,又称init)

linux 如何清理僵尸进程

今天在维护服务器的时候,发现有5个nova-novncproxy的僵尸进程. 26327 ?        S      0:05  \_ /usr/bin/python /usr/bin/nova-novncproxy --config-file=/etc/nova/nova.conf 4765 ?        Z      0:00      \_ [nova-novncproxy] <defunct> 4766 ?        Z      0:00      \_ [nova-no

一起聊聊 Linux 系统中的僵尸进程

Linux 系统中僵尸进程和现实中僵尸(虽然我也没见过)类似,虽然已经死了,但是由于没人给它们收尸,还能四处走动.僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸.僵尸进程如何产生的?如果一个进程在其终止的时候,自己就回收所有分配给它的资源,系统就不会产生所谓的僵尸进程了.那么我们说一个进程终止之后,还保留哪些信息?为什么终止之后还需要保留这些信息呢?一个进程终止的方法很多,进程终止后有些信息对于父进程和内核还是很有用的,例如进程的ID号.进程的退出状态.进程运行的

Linux 僵尸进程查杀

僵尸进程概念 僵尸进程(Zombie process)通俗来说指那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸. 书面形式一点:一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,那么他将变成一个僵尸进程.通过ps命令查看其带有defunct的标志.僵尸进程是一个早已死亡的进程,但在进程表(processs table)中仍占了一个位置(slot). 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程.因为每个进程结束的时候,系统都会扫描

【Linux】孤儿进程和僵尸进程

本文转载自:http://www.cnblogs.com/anker/p/3271773.html 并做了小幅修改. 1.基本概念 我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程.子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态. 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些

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进程实践(2) --僵尸进程与文件共享

孤儿进程与僵尸进程 孤儿进程: 如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程.(注:任何一个进程都必须有父进程) //生成孤儿进程 int main(int argc, char *argv[]) { pid_t pid = fork(); if (pid < 0) err_exit("fork error"); else if (pid > 0) exit(0); else { sleep(10); cout << "Chil

Linux进程实践(4) --wait避免僵尸进程

Wait的背景 当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止) 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态. 父进程查询子进程的退出状态可以用wait/waitpid函数 #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); pid_t wa