iis服务器助手广告广告
返回顶部
首页 > 资讯 > 操作系统 >【Linux取经路】探索进程状态之僵尸进程 | 孤儿进程
  • 791
分享到

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

linux服务器数据库进程状态运维 2023-09-03 13:09:32 791人浏览 泡泡鱼
摘要

文章目录 一、进程状态概述1.1 运行状态详解1.2 阻塞状态详解1.3 挂起状态详解 二、具体的Linux操作系统中的进程状态2.1 Linux内核源代码2.2 查看进程状态2.3 D

在这里插入图片描述

文章目录

一、进程状态概述

进程状态是指在操作系统中,一个进程所处的不同运行状态,进程状态就决定了该进程接下来要执行什么任务。常见的进程状态有以下几种:

  • 新建状态:进程被创建但还没有被操作系统接受和分配资源。

  • 就绪状态:进程已经获得了所需的资源,并等待被调度执行。

  • 运行状态:进程正在执行指令,占用CPU资源。

  • 阻塞状态:进程因等待某个事件(如io操作)而暂时停止执行,并释放CPU等资源。

  • 终止状态:进程执行完成或被终止,释放所有资源。

1.1 运行状态详解

在上一篇文章【Linux取经路】揭秘进程的父与子提到过,一般的计算机中只有一个 CPU,而进程却可能有很多个,这就注定了 CPU 是一个少量的资源,对所有的进程来说,运行的本质,就是把它放到 CPU 上,所以每个 CPU 都会维护一个运行队列,CPU 以队列的形式对进程做调度。所有的进程要运行都要在运行队列中排队,参与排队的是每个进程的 PCB 对象。所有在运行队列中的进程,它们所处的状态就叫做运行态(R状态)。

在这里插入图片描述
一个进程只要把自己放到 CPU 上开始运行,并不是一直要执行完毕,才把自己放下来。如果一个进程被放到 CPU 上直到执行完毕才把自己放下来继续去执行其他进程,那当我们的程序中写了一个 while 死循环出来,在运行该程序的时候,其他的应用就会卡住。但现实并不是这样,我们写了一个 while 死循环,其他程序照样可以正常运行。为了避免这种一个进程长时间占用 CPU 资源的情况出现,提出了时间片的概念。

时间片是操作系统中任务调度算法的一种思想,即将 CPU 的执行时间划分成固定长度的时间段,每个时间段称为一个时间片。在每个时间片内,操作系统将 CPU 分配给一个任务进行执行,当时间片耗尽时,操作系统会中断当前任务,并将 CPU 分配给下一个任务。时间片一般是10毫秒左右,所以在一个时间段内所有的进程代码都会被执行,我们将这种情况叫做并发执行。这种情况下会有大量的把进程放上 CPU 和从 CPU 拿下来的动作,这就叫做进程切换。

1.2 阻塞状态详解

最常见的阻塞状态就是一个进程需要通过键盘读取数据。当一个进程等待从键盘输入的过程,此时该进程就处在阻塞状态。键盘是一种硬件,在冯诺依曼结构体系中属于输入设备(外设),操作系统对硬件资源的管理是先描述再组织,因此每一个硬件都会对应一个结构体对象,该结构体对象中一定会维护一个等待队列,当一个进程需要利用该硬件资源时,进程的 PCB 对象就会被链入该等待队列,此时进程就处于阻塞状态。

在这里插入图片描述

小Tips:操作系统中的等待队列可能有成百上千个,不仅每一种硬件有等待队列,进程中也有等待队列,可能会出现一个进程等待另一个进程结束后才能继续运行。不同的操作系统,调度算法也会不同。

1.3 挂起状态详解

在一些操作系统的教材上还会出现挂起状态。无论是运行状态还是阻塞状态,一个进程在没有被 CPU 调度的情况下,它的代码和数据是处于空闲的,即没有被使用。之前说过一个进程在内存中有它自己的代码和数据,还有自己的 PCB 对象,当内存空间告急时,操作系统就会把这些没有被 CPU 调度的进程的代码和数据先放到磁盘中存储,只留进程的 PCB 对象在队列中排队,这种进程就处于挂起状态。

在这里插入图片描述
上面介绍的这些属于操作系统学科的理论知识,不同的操作系统可能会有不同的实现方案,下面我们来深入看看具体的 linux 操作系统中有哪些进程状态。

小Tips:挂起状态对用户是不可见的,这是操作系统的一种行为。就像我们把钱存银行里,我们并不知道银行把我们的钱拿去干嘛了,银行可能把我们的钱借出去了或者给员工发工资了等等,我们作为客户不得而知,我们只知道如果存的是活期,可以随时到银行把钱取出来,如果存的是死期只有到期了才能取出来。

二、具体的Linux操作系统中的进程状态

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux 内核里,进程有时候也被叫做任务)。

2.1 Linux内核源代码

static const char * const task_state_array[] = {"R (running)", "S (sleeping)", "D (disk sleep)", "T (stopped)", "t (tracing stop)", "X (dead)", "Z (zombie)", };
  • R运行状态(running):并不意味着进程一定在运行中,它表明进程要么是在运行中要么是在运行队列里。

  • S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep)),它对应操作系统理论中的阻塞状态。

  • D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待 IO 的结束。

  • T停止状态(stopped):可以通过发送 SIGSTOP 信号给进程来(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。

  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

2.2 查看进程状态

先来看看下面这段代码执行起来后的进程状态。

int main()                        {         while(1)                          {             printf("Hello Linux\n!");       }        return 0;    }  

在这里插入图片描述
可以看出这段代码执行起来后的进程状态是 S睡眠状态,将 while 循环中的打印去掉再去执行代码看看进程状态。

int main()                        {         while(1);                          return 0;    }  

在这里插入图片描述
此时代码中只剩一个 while 死循环,去执行这段代码,进程状态变成了 R运行状态。为什么会出现在这种情况呢?原因是 CPU 的执行速度是非常快的,第一段代码中的 printf 是要去频繁的访问显示器设备,而我们的显示器可能并不能被该进程直接去写入,所以该进程大部时间都在显示器的等待队列里等待显示设备就绪,因此最终查出来的进程状态是 S睡眠状态。当我们去掉 printf 之后,该进程就不会去访问显示器设备,始终都在运行队列里,所以最终查出来的进程状态是 R运行状态。

小Tips:查询结果中显示的+表示该进程在前台运行,这意味我们此时在 bash 命令行输指令是不会有任何反应的,可以在输入指令的后面加上&,此时表示让该进程在后台运行,要终止掉该进程只能通过指令kill -9 进程PID

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

在这里插入图片描述
可以通过下面这条指令来结束停止状态。

kill -18 进程PID//

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

在这里插入图片描述

三、僵尸进程

一个进程在退出时并不是立即将自己所有资源全部释放,当一个进程退出时,操作系统会把当前进程的各种信息维持一段时间,这个状态就叫做 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 函数就是用来终止一个进程,父进程将一直运行。

在这里插入图片描述
子进程执行完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;    }

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

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

五、结语

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

在这里插入图片描述

来源地址:https://blog.csdn.net/weixin_63115236/article/details/132274130

--结束END--

本文标题: 【Linux取经路】探索进程状态之僵尸进程 | 孤儿进程

本文链接: https://www.lsjlt.com/news/392189.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作