僵尸进程与孤儿进程

前言

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

进程在创建的时候开始存活,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()系统调用回收他的进程描述符的信息,该进程会继续停留在系统的进程表中,占用内核资源,这样的进程就是僵尸进程。

接下来通过一个案例构造一个僵尸进程。

#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 %d\n",getpid());
        sleep(30);//睡眠30s,在父进程之前退出
        printf("child finally...");
    }
    else { //父进程
        printf("parent id is %d\n",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 %d\n",getpid());
        sleep(100);
    }
    else { //父进程
        printf("parent id is %d\n",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命令将进程杀死该进程的父进程来结束僵尸进程。当然也要结合具体场景来对待。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在 Linux 中,僵尸进程孤儿进程都是指与父进程不再有联系的进程,它们通常是由于进程管理不当或程序错误导致的。 **僵尸进程**是已经完成执行任务,但其父进程还没有来得及处理其退出状态的进程。当进程完成执行后,它的退出状态(也称为退出码或终止状态)会被保存在系统中,直到父进程通过 `wait` 或 `waitpid` 等函数来获取该状态。如果父进程没有处理该状态,那么该进程就会成为僵尸进程,占用系统资源。要清理僵尸进程,可以使用 `kill` 命令向其父进程发送 `SIGCHLD` 信号,或者重新编写程序,使其正确处理子进程的退出状态。 **孤儿进程**是指其父进程已经退出或被终止,但其自身仍在运行的进程孤儿进程会被 `init` 进程进程号为 `1`)接管,`init` 进程会定期检查系统中是否有孤儿进程,并且将其的父进程设置为 `init` 进程。要避免孤儿进程的产生,可以在父进程退出之前,等待子进程的退出,或者将子进程的父进程设置为 `init` 进程。 可以使用 `ps` 命令来查看系统中的僵尸进程孤儿进程。使用以下命令可以查看所有僵尸进程: ``` ps aux | grep 'Z' ``` 其中,`aux` 选项用于显示所有进程,`grep 'Z'` 用于查找所有状态为 `Z` 的进程,即僵尸进程。 使用以下命令可以查看所有孤儿进程: ``` ps -ejH ``` 其中,`-e` 选项用于显示所有进程,`-j` 选项用于以层次结构的形式显示进程,`-H` 选项用于显示所有孤儿进程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凤舞飘伶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值