僵尸进程与孤儿进程,以及如何避免僵尸进程

僵尸进程与孤儿进程

这部分参考了: https://www.cnblogs.com/Anker/p/3271773.html

  linux提供一种机制使子进程在退出时候,父进程能够收集到子进程的结束状态信息(子进程pid,退出状态,运行时间等)。父进程需要调用 wait、waitpid来获取这些信息。父进程收集这些信息后这些信息才会释放。

  linux下新进程的创建可以由 clone、fork、vfork(注意与 fork 的区别,执行顺序,vfork 场景下子进程不能调用 return,也不能调用 exit,建议调用 _exit或者 exec 族函数)来产生新的子进程。然后根据fork的返回值(小于0,等于0,大于0)判断是fork出错,子进程还是父进程。通常情况下,父进程需要在子进程任务结束退出后做“善后”(参见linux 信号机制),也就是一些资源清理工作。子进程退出时会把打开的文件句柄,内存占用,打开的资源进行释放,但是不会清理进程控制块PCB信息。

孤儿进程:

  孤儿进程意思是父进程早于子进程退出。此时子进程会成为孤儿进程。linux会对孤儿进程的处理,把孤儿进程的父进程设为1(新的内核设置成 systemd ),也就是由init进程(在最新的 Linux 是 systemd)来托管。init 进程负责子进程退出后的善后清理工作。

这其中有一层意思:在父进程先退出了的情况下子进程默认变成孤儿进程,子进程的父进程变为 systemd 或者 init进程

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

int main(int argc, char **argv)
{
    pid_t pid = fork();

    signal(SIGCHLD, SIG_IGN); // 父进程先退出,这个信号就不重要了
    if (pid < 0)
        printf("error fork\n");
    else if (pid == 0)
    {
        do
        {
            printf("child pid = %ld, ppid = %ld, gid = %ld\n", getpid(), getppid(), getpgid(getpid()));
            sleep(1);
        } while (1);
    }
    else
    {
        printf("parent pid = %ld, gid = %ld\n", getpid(), getpgid(getpid()));
        do
        {
            sleep(1);
        } while (0);
        printf("parent pid = %ld quit\n", getpid());
    }

    return 0;
}


如上图可以发现,父进程退出后子进程的新的父进程变成了 systemd。

僵尸进程:

  子进程退出后留下的进程信息没有被收集,会导致占用的进程控制块PCB不被释放,形成僵尸进程。进程已经死去,但是进程资源没有被释放掉。

僵尸进程出现情况是:父进程没有为子进程”收尸“,意思是父进程即没有设置忽略 SIGCHLD 也没有调用 wait、waitpid 来释放资源。

问题及危害

  如果系统中存在大量的僵尸进程,他们的进程号就会一直被占用,但是系统所能使用的进程号是有限的,系统将因为没有可用的进程号而导致系统不能产生新的进程.。
  任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个 子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状程的身份对僵尸状态的子进程进行处理。

怎么避免僵尸进程

  1. 通过 signal(SIGCHLD, SIG_IGN) 通知内核对子进程的结束不关心,由内核回收。
  2. 父进程主动调用 wait、waitpid 等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。如果父进程很忙可以用 signal 注册信号处理函数,在信号处理函数调用wait、waitpid等待子进程退出。
  3. 杀死父进程。 如果僵尸进程的父进程还存在,找到这个父进程,kill掉它。这样就会变成2的情况,init或者 systemd会负责善后工作。
posted @ 2019-05-23 17:25  sinpo828  阅读(1450)  评论(0编辑  收藏  举报