专栏首页Java冰冻三尺面试官问:孤儿进程和僵尸进程,你造吗~
原创

面试官问:孤儿进程和僵尸进程,你造吗~

那时刚写公众号,当时记录的学习笔记,现在看来,之前记录的有一个错误的地方,当时也没察觉到。

写错了就要改嘛,程序员也不能怕错~

不知道大家看自己几年前的做事情,有的时候有没有一种感觉,这是我做的吗???

好吧,有点嫌弃当时的自己~

直接进入正题吧,父子进程之间到底有啥关系?

进程

先来说下什么是进程:

来看下百度是怎么说的:

光看说的不够形象,在windows系统中,它长这样:

在Mac系统中,它长这样:

Linux中是这样的:(有点长截图一部分好了)

[root@iz2ze76ybn73dvwmdij06zz ~]# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 5月20 ?       00:00:33 /usr/lib/systemd/systemd --system --deserialize 21
root         2     0  0 5月20 ?       00:00:00 [kthreadd]
root         3     2  0 5月20 ?       00:00:06 [ksoftirqd/0]
root         5     2  0 5月20 ?       00:00:00 [kworker/0:0H]
root         7     2  0 5月20 ?       00:00:02 [migration/0]
root         8     2  0 5月20 ?       00:00:00 [rcu_bh]
root         9     2  0 5月20 ?       00:30:40 [rcu_sched]
root        10     2  0 5月20 ?       00:00:17 [watchdog/0]
root        11     2  0 5月20 ?       00:00:16 [watchdog/1]
root        12     2  0 5月20 ?       00:00:02 [migration/1]
root        13     2  0 5月20 ?       00:00:03 [ksoftirqd/1]
root        15     2  0 5月20 ?       00:00:00 [kworker/1:0H]
root        17     2  0 5月20 ?       00:00:00 [kdevtmpfs]
root        18     2  0 5月20 ?       00:00:00 [netns]
root        19     2  0 5月20 ?       00:00:01 [khungtaskd]
root        20     2  0 5月20 ?       00:00:00 [writeback]
root        21     2  0 5月20 ?       00:00:00 [kintegrityd]
root        22     2  0 5月20 ?       00:00:00 [bioset]
root        23     2  0 5月20 ?       00:00:00 [kblockd]

OK,以上每一行都是对一个进程的描述,来具体看一下每个参数的含义:

标示

描述

UID

用户ID

PID

进程ID

PPID

父进程ID

C

进程占cpu百分比

STIME

进程启动的时间

TTY

终端机位置

TIME

实际使用cpu的时间

CMD

命令以及参数

我们现在知道了每个参数的含义,既然讲到进程嘛,首先,进程ID是唯一的并且是非负数的,但是进程ID是可以复用的,毕竟进程也会终止。

可以看到没有进程PID是0的,这是为什么呢?

黑人问号脸?

0一般来说是系统进程,属于内核的一部分,不执行任何磁盘上的程序。

fork

一个进程可以通过调用fork函数创建新的进程,被创建出来的这个进程就叫子进程。

这里需要注意一下,fork函数的返回值父子进程区别。

  • 子进程 : 返回值是0,返回0的理由是子进程的父进程是可以唯一确定的,通过getppid方法可以获取到父进程id。
  • 父进程 : 返回的是新创建的子进程的id,因为父进程可以有多个子进程,也没有这样的函数可以获取该线程的子线程的所有id。

下边的话我们来验证一下上说的这一段话。准备好脚本。

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

int main(int argc, char const *argv[])
{

    pid_t p1 = fork();

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

    if(p1 > 0)
    {
        printf("父进程 pid = %d, p1 = %d\n", getpid(), p1);
    }
    else
    {
        printf("子进程 pid = %d , ppid = %d, p1 = %d\n", getpid(), getppid(), p1);
    }

    return 0;
}

运行看结果:

[root@iz2ze76ybn73dvwmdij06zz ~]# ./fork2
10213
父进程 pid = 10212, p1 = 10213
0
子进程 pid = 10213 , ppid = 10212, p1 = 0

通过上面的小例子我们可以看到父进程的返回值是子进程的ID,子进程的返回是0。

孤儿进程

孤儿我们都懂就是。。。

是的,没错孤儿进程也是这样的,就是没有父进程的进程。当然创建的时候肯定是要先创建父进程了,当父进程退出时,它的子进程们(一个或者多个)就成了孤儿进程了。

接下来在继续做一个小测试,让父进程现退出。准备好脚本。

[root@iz2ze76ybn73dvwmdij06zz ~]# cat guer.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>


int main()
{
    pid_t pid = fork();

    if (pid < 0) {
	perror("fork error;");
        exit(1);
    } else if (pid == 0) {
	sleep(5);
        printf ("子进程 : [ pid] = %d , 父进程 [ppid] = %d\n",getpid(),getppid());
        exit(0);

    } else if (pid > 0) {
	printf("我是父线程,我先退出一步~\n");
	exit(0);

    }
  return 0;
}

执行并看结果:

到这里估计很多童鞋估计已经看懂了,父进程退出后,子进程被一个进程ID为1的进程领养的。还挺好这个结果,至少还是有人管的,被暖到了~ 进程id为1的进程是init进程,每当有孤儿进程出现时,init进程就会收养它并成为它的父进程

,来照顾它以孤儿进程以后的生活。

危害

因为孤儿进程会被init进程接管,所以孤儿进程是没有危害的。

僵尸进程

和孤儿进程相反的是,这次是子进程先退出,而父进程又没有去处理回收释放子进程的资源,这个时候子进程就成了僵尸进程。

先准备好代码:

[root@iz2ze76ybn73dvwmdij06zz ~]# cat zombie.c
  #include <stdio.h>
  #include <unistd.h>
  #include <errno.h>
  #include <stdlib.h>

  int main()
  {
      pid_t pid;
      pid = fork();
     if (pid < 0)
     {
         perror("fork error:");
         exit(1);
     }
     else if (pid == 0)
     {
         printf("我是子进程,我要先退出一步了.\n");
	 printf("子进程 id : %d\n" ,getpid());
         exit(0);
     } else {
         printf("我是父进程,我先睡2秒\n");
         printf("父进程 id : %d\n" ,getpid());

         sleep(2);

         while(2); //来个死循环,不退出的那种
    }


   return 0;
 }

运行看下结果:

再来开一个终端看下进程结果:

大家可以看到,子进程并没有完全退出,释放资源,而是变成了僵尸进程。

危害

资源上是占用不了什么资源。但是通常系统的进程数量都是有限制的,如果有大量的僵尸进程占用进程号,导致新的进程无法创建,这个危害类似于占个坑,不办事。

处理

1.干掉父进程

干掉父进程后,让剩下的子进程成为孤儿进程,成为孤儿进程后就和我们上面说的一样了,由init进程来领养这些进程,并且来处理这些进程的资源释放等工作。

2.父进程调用wait或waitpid

等函数等待子进程结束,这会导致父进程挂起。

执行wait()或 waitpid()系统调用,则子进程在终止后会立即把它在进程表中的数据返回给父进程,此时系统会立即删除该进入点。在这种情形下就不会产生defunct进程。

3.fork两次

第一次 fork : 父进程fork一个子进程

第二次 fork : 子进程fork一个孙进程后退出

那么孙进程被init接管,当孙进程结束后,init会回收。

但子进程的回收还要自己做。

4.signal函数

父进程来处理:用signal函数为SIGCHLD安装handler,在子进程结束后,父进程会收到该信号,可以在handler中调用wait回收。

内核来处理:

如果父进程不关心子进程什么时候结束,可以通过以下两个函数通知内核自己不感兴趣子进程的结束,此时,子进程结束后,内核会回收并不再给你父进程发信号。

  • signal(SIGCLD, SIG_IGN)
  • signal(SIGCHLD, SIG_IGN)

总结

本来以为简单的一个问题,没想到这个篇幅其实也不算短,所以感觉程序员真的不能说一个什么知识点就简单,很容易理解啊,一旦你想要深入也是需要一定的时间花费和精力的~

参考:

微信搜索:【Java小咖秀】更多精彩等着您~

回复手册获取博主15万字Java面试通关手册&2万字Linux命令实战书册~

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • java程序员必备的Intellij插件

    我太懒了,必须要用Intellij插件减少我的工作量。以下是我用过不错的Intellij插件

    Java小咖秀
  • 面试官问:​如何设计一个安全的对外接口?

    最近有个项目需要对外提供一个接口,提供公网域名进行访问,而且接口和交易订单有关,所以安全性很重要;这里整理了一下常用的一些安全措施以及具体如何去实现。

    Java小咖秀
  • 30s部署一个博客系统【附带源码+教程】

    Java小咖秀
  • 002.KVM环境部署

    木二
  • Linux 进程管理

    进程是 UNIX/Linux 用来表示正在运行的程序的一种抽象概念,所有系统上面运行的的数据都会以进程的形态存在。

    用户1679793
  • 漫画:排序算法系列 第一讲(利用插入算法思想解题)

    在leetcode中,直接搜索排序标签出现的题目有80余道,这是与排序直接相关的题目,不包括其他一些用到排序思想的题目。

    程序员小浩
  • [Linux] Linux系统(进程管理)

    进程:当我们运行程序时,Linux会为程序创建一个特殊的环境,包含程序运行的所有资源,这个环境就称为进程

    陶士涵
  • Docker镜像与容器的常用操作

    国内从 Docker Hub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。国内很多云服务商都提供了国内加速器服务,例如:

    踏歌行
  • Linux系统集群架构线上项目配置实战(三)

    前面介绍了分发文件管理、前端反向代理与管理后台服务器的配置操作,今天介绍下前端负载与APP服务器(用户与商家)、官网、FTP服务器的配置操作

    民工哥
  • 问答 | 欧拉角中从物体坐标系向世界坐标系与从世界坐标系向物体坐标系旋转一样吗?

    AI研习社

扫码关注云+社区

领取腾讯云代金券