关于linux:一文搞懂孤儿进程和僵尸进程

38次阅读

共计 2999 个字符,预计需要花费 8 分钟才能阅读完成。

微信公众号:LinuGo,欢送关注获取更多详情

前言

过程就是运行起来的一个程序,然而过程并不局限于执行起来的代码,他的作用范畴还有很多,如存放数据的内存地址空间,执行线程,关上的文件,挂起的信号,处理器状态等。

过程在创立的时候开始存活,Linux 零碎会调用 fork() 办法复制一个现有过程来创立一个全新的过程,新产生的过程为子过程,创建者过程为父过程。当程序完结运行时,通过 exit() 零碎调用退出执行,该过程占用的资源包含内存空间,线程等被开释掉。

过程家族树

过程都是由其余过程创立进去的,每个过程都有本人的 PID(过程标识号),在 Linux 零碎的过程之间存在一个继承关系,所有的过程都是 init 过程的后辈。能够通过 pstree 命令查看到过程的族谱。零碎中的每个过程必定会有一个父过程,能够在 /proc 文件系统中看到过程对应的父过程号,也能够通过 ps -ef 命令。

Linux 过程波及过程的所有操作都围绕过程描述符开展,就是 task_struct 构造体。构造体蕴含好几百个字段,该构造体能够残缺的形容一个正在执行的程序,如虚拟地址空间的信息,关上的文件,过程的执行状态和信息,命名空间,身份信息等。

在每个过程的 task_struct 中,用字段 parent 示意该过程的父过程,sibling 代表兄弟过程链表,children 代表子过程链表,实践上能够通过每一个过程获取任何过程。

// 构造体中相干字段
struct task_struct{
    ...
    struct task_struct *parent; /* 父过程 */ 
    struct list_head children; /* 子过程链表 */ 
    struct list_head sibling; /* 连贯到父过程的子过程链表,兄弟过程 */ 
    ....
}

过程终结

当过程终结时候,零碎须要开释他所占有的所有资源。过程通过 exit() 零碎调用完结过程,这个调用可能是来自过程外部的 exit(),也可能来自内部的信号。在完结时候,该过程会应用该零碎调用开释本人的空间,包含援用的文件,内存描述符,还会给本人的父过程发送信号,给本人的子过程寻找一个父过程等操作。

调用完结后,此时该过程并没有齐全从零碎上隐没,过程的过程描述符仍然存在于零碎中,存在的惟一目标就是向父过程提供信息。

与自然规律相同,过程的收尾工作总是由该过程的父过程来做的,父过程会通过 wait() 零碎调用来开释该过程最初残余的过程标识符,slab 缓存等,该调用会阻塞以后父过程,直到某个子过程退出。

对于过程退出,能够联合看一下 Linux bash 怎么做的。

  • 首先 ps 命令获取 bash 的过程 PID

  • 再开一个 bash 页面,查看上个 bash 过程的零碎调用
$ sudo strace -p <Pid>

能够看到该过程的零碎调用曾经被捕捉。

  • 输出一条命令回车,察看零碎调用状况

这里一条命令相当于一个 bash 的子过程,这里以 tail 为例。能够看到阻塞到该零碎调用,也就是在期待回收子过程。

应用 Ctrl+ C 完结过程 tail 时候,返回了 tail 过程的 Pid。

理解了这些后,接下来了解僵尸过程和孤儿过程就很容易了。上面通过案例解说一下。


僵尸过程

当过程 exit() 退出之后,他的父过程没有通过 wait() 零碎调用回收他的过程描述符的信息,该过程会持续停留在零碎的过程表中,占用内核资源,这样的过程就是僵尸过程。

接下来通过一个 demo 结构一个僵尸过程。

#include <unistd.h>
#include <stdio.h>

int main ()
{
    /*fpid 示意 fork 函数返回的值,fork 会返回两次,一次是父过程,返回值是子过程的 Pid,在子过程会返回 0 */
    pid_t fpid;
    fpid=fork();//fork 后会呈现两个分支执行上面的代码,一个父过程,一个新的子过程
    if (fpid < 0)
        printf("fork error!");
    else if (fpid == 0) { //
        printf("child id is %dn",getpid());
        sleep(30);// 睡眠 30s,在父过程之前退出
        printf("child finally...");
    }
    else { // 父过程
        printf("parent id is %dn",getpid());
        sleep(60);
        printf("parend finally...");
    }
}
  • 编译并运行这段代码。
$ gcc -o corpse corpse.c
$ ./corpose

  • pstree 查看过程树
$ pstree -p 9106

  • 期待子过程退出,查看子过程的状态
$ cat /proc/<Pid>/status

等到父过程退出之后,再来查看零碎,该僵尸过程在零碎中找不到了。

父过程退出,没有为子过程”收尸”,然而子过程也会一并退出,怎么做到的呢?这就波及到了孤儿过程。


孤儿过程

当一个过程正在运行时,他的父过程突然退出,此时该过程就是一个孤儿过程。作为一个过程,须要找到一个父过程,否则这种过程在退出之后没人回收他的过程描述符,空耗内存。此时该过程会找到一个父过程,如果本人所在的过程组没人收养,那就作为 init 过程的子过程。

结构一个测试代码:

#include <unistd.h>
#include <stdio.h>

int main ()
{
    /*fpid 示意 fork 函数返回的值,fork 会返回两次,一次是父过程,返回值是子过程的 Pid,在子过程会返回 0 */
    pid_t fpid;
    fpid=fork();//fork 后会呈现两个分支执行上面的代码,一个父过程,一个新的子过程
    if (fpid < 0)
        printf("fork error!");
    else if (fpid == 0) { //
        printf("child id is %dn",getpid());
        sleep(100);
    }
    else { // 父过程
        printf("parent id is %dn",getpid());
        sleep(30);// 睡眠 30s,在子过程之前退出
        printf("parend finally...");
    }
}
  • 编译并运行
$gcc -o orphan orphan.c
$./orphan

  • pstree 查看过程树

  • 等到父过程退出再看过程信息

能够看到看到该过程的父过程变为 1,也就是 init 过程。

Init 过程会为每一个子过程应用 wait 零碎调用,确保不会产生僵尸过程。这里的 wait 零碎调用指的是 waitpid(),会传入一个要期待的过程 Pid,期待的指定过程,而不阻塞以后过程去期待。

等到该过程退出后,该过程的过程描述符等信息会被 init 过程回收,不会造成僵尸过程。

回到上一个僵尸过程的案例中,父过程退出掉后,该过程会找到 init 作为父过程,init 过程针对给过程调用 wait() 零碎调用回收了该子过程。这就是查问不到这个过程的起因。


处理形式

孤儿过程会由 init 过程收养作为子过程,所以不会有什么危害;僵尸过程会占用过程号,以及未回收的文件描述符占用空间,如果产生大量的僵尸过程,将会导致系统无奈调配过程号,阐明父过程的代码编写有问题。

$ ps -aux|grep Z

在现实状况下,能够通过 kill 命令将过程杀死该过程的父过程来完结僵尸过程。当然也要联合具体场景来看待。


微信关注 LinuGo 回复书名即可获取下方书籍 pdf 版

参考书籍

  • 《Linux 内核设计与实现》第三章. 过程治理
  • 《深刻 Linux 内核与架构》第二章. 过程治理与调度
  • 《后盾开发核心技术与利用实现》第十章. 过程

正文完
 0