【Linux取经路】探索进程状态之僵尸进程 | 孤儿进程(二)

简介: 【Linux取经路】探索进程状态之僵尸进程 | 孤儿进程(二)

2.3 D磁盘休眠状态(Disk sleep)

D状态也是一种阻塞状态,在 Linux 系统层面我们称作深度睡眠,S状态称作浅度睡眠。浅度睡眠是可以被唤醒的,即可以响应外部的变化,我们可以通过 kill 指令(其他进程)将浅度睡眠的进程终止掉。下面通过一个情景剧来给大家介绍为什么要有 D 状态,以及 D 状态的作用。

有这样一个场景,一个进程需要向磁盘中写入大量数据。在正常情况下往磁盘中写入数据,进程是需要等待的,等磁盘写完后给进程一个信号,然后进程才能继续去运行。有一天进程A就在向磁盘中写入大量数据,磁盘在写入的过程中,进程A就在内存中翘着二郎腿,嗑着瓜子在等待磁盘写完了给它发信息,此时路过的操作系统发现了进程A,它对进程A说:“我这内存压力都大的不行了,你小子倒好,占着内存不干正事,还在这嗑瓜子!”。于是乎操作系统就将进程A kill 掉了。此时磁盘傻眼了,数据写到一半进程没了,因为进程没了,所以磁盘就把写入的数据删除了,最终结果就是数据没有被写入磁盘。究竟是谁导致了这场悲剧的发生呢?于是乎法官就出来,它先审问操作系统,进程是你 kill 掉的,你怎么解释?操作系统说,我命苦呀,我只是完成了我的本职工作呀,为了给用户提供流畅的运行环境,将一些进程 kill 掉是我的职责呀,这不是我的问题呀。接着法官又来问磁盘,数据是你丢失的,你该如何解释?磁盘说,我祖祖辈辈都是这样工作的呀,进程它让我写入数据,结果自己不见了,其它磁盘遇到这种情况也是将数据丢弃掉呀,你如果判我有罪,那岂不是我的父亲、母亲都有罪呀。最后法官来问进程A,进程还没等法官开口就扑通跪下说,法官大人您明察秋毫呀,我才是被 kill 掉的那个,我属于被害人呀,我怎么会有罪呢。法官听了一圈,感觉大家都没罪,最终法官宣判了,你们三个都没罪,是制度问题,回去我改改操作系统,当进程在向磁盘中写入数据的时候任何人都不能将该进程 kill 掉。于是 D 状态就诞生了。当一个进程处于 D 状态的时候,它不会响应任何请求,任何人和操作系统都不能将该进程 kill 掉。

小Tips:结束掉 D 状态的方法有两种,一是等待某个条件满足,如等待数据写完,二是直接断电。如果被用户查到 D 状态的进程,那就预示着这个操作系统离崩溃不远啦。所以 D 状态会有,但是一般出现的时间都非常短。

2.4 T停止状态(stopped)

在 Linux 内核源代码中我们可以看到连个 T 状态,一个是 T ,一个是 t,我们可以认为这两个 T 状态是一样的,对于一个进程,我们可以通过下面这条指令将它设置成停止状态。

kill -19 进程PID

49f0cfc29f81497b907fdd0a4502abd8.png

可以通过下面这条指令来结束停止状态。

kill -18 进程PID//


212a3b76a1c34374b6356dea89578c08.png

小Tips:结束停止状态的进程会到后台运行,要终止掉这个进程只能通过 kill -9指令。T状态和S状态很像,其中S状态的进程一定是在等待某种资源,而T状态的进程可能是在等待某种资源,也可能是在被其他进程控制。我们在打断点调试一段代码的时候,该进程就会处于T状态。


a795e2def62e43e08a4fff0a33aa7490.png

三、僵尸进程

一个进程在退出时并不是立即将自己所有资源全部释放,当一个进程退出时,操作系统会把当前进程的各种信息维持一段时间,这个状态就叫做 Z 僵尸状态。维持信息是给关心它的“人”,也就是父进程来查看的。如果父进程一直没有来关心退出的子进程,那么这个子进程将长时间处于 Z 状态。

int main()    
{    
    pid_t id = fork();    
    if(id == 0)    
    {    
        int cnt = 5;    
        while(cnt)    
        {    
            printf("我是子进程,PID是:%d,PPID:%d,cnt:%d\n",getpid(),getppid(),cnt);                    
            sleep(1);    
            cnt--;    
        }    
       _exit(0);    
    }    
    else 
    {
        while(1)
        {
            printf("我是父进程,PID是:%d,PPID:%d\n",getpid(),getppid());
            sleep(1);
        }
    }
    return 0;
}

上面这段代码在 process 进程中通过调用 fork 接口创建了一个子进程,子进程在执行完五次打印后就会被终止掉,其中的 exit 函数就是用来终止一个进程,父进程将一直运行。

00fee6c0434c486bb742899ca39bc1f2.gif


子进程执行完5次打印后就处于 Z 状态并且后面跟了一个单词 defunct,该单词有死了的,不存在的意思,只不过它还再等父进程来回收它的资源。处于 Z 状态的进程的相关资源尤其是 task_struct 结构体不能被释放。只有当父进程把子进程的相关资源回收后,子进程才能变成 X死亡状态。我们将这种处于 Z 状态的进程就叫做僵尸进程,如果父进程一直不来回收,那这种进程会长时间占用内存资源,造成内存泄漏。

3.1 僵尸进程危害总结

  1. 进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就将一直处于 Z 状态。
  2. 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在 PCB 对象中,换句话说,Z状态一直不退出,PCB一直都要维护。
  3. 一个父进程如果创建了很多的子进程,就是不回收,会造成内存资源的浪费,因为 PCB 对象本身就要占用内存。
  4. 造成内存泄漏。

四、孤儿进程

上面我们是让子进程先退出,父进程一直运行,接下来我们让父进程先退出,子进程一直运行,看看会有什么结果。

int main()    
{    
    pid_t id = fork();    
    if(id == 0)    
    {
      //子进程    
        int cnt = 500;    
        while(cnt)    
        {    
            printf("我是子进程,PID是:%d,PPID:%d,cnt:%d\n",getpid(),getppid(),cnt);    
            sleep(1);    
            cnt--;    
        }    
       _exit(0);    
    }    
    else    
    {
      //父进程    
        int cnt = 5;
        //这里的cnt是5,意味着父进程会先执行结束    
        while(cnt--)    
        {    
            printf("我是父进程,PID是:%d,PPID:%d,cnt:%d\n",getpid(),getppid(),cnt);
            sleep(1);                                                                        
        }    
    }    
    return 0;    
}


ffa7ea09ac3649c7a363bd92b3b508f3.gif

可以看到父进程在执行结束后就只剩下子进程,为什么父进程不会处在 Z僵尸状态呢?答案是父进程也是 bash 的子进程,父进程在执行结束后,它的父进程 bash 会将其回收掉,并且过程非常快,所以我们我们没有看到父进程处在 Z僵尸状态。其次我们发现,当父进程结束后,它的子进程的父进程会变成1号进程,即操作系统。我们将父进程是1号进程的进程叫做孤儿进程,该进程被系统领养。因为孤儿进程未来也会退出,也要被释放,所以它需要被领养。

小Tips:所有的进程只对它的“儿子”,即子进程负责,不会对它的孙子进程负责,因为代码中只有创建子进程的逻辑,并没有创建孙子进程的逻辑,所以并不是不想让爷爷进程来回收孙子进程的资源,是因为爷爷进程没有这个本事,而操作系统会直接从内核层面进行回收,所以当一个进程的父进程结束后,会把该进程交给操作系统,让操作系统来充当它的父进程。

五、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!

目录
相关文章
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
616 32
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
542 67
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
525 16
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
342 20
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
855 34
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
407 0
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
457 0
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
335 0
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
426 0

热门文章

最新文章