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

news2024/11/25 7:14:33

在这里插入图片描述

文章目录

  • 一、进程状态概述
    • 1.1 运行状态详解
    • 1.2 阻塞状态详解
    • 1.3 挂起状态详解
  • 二、具体的Linux操作系统中的进程状态
    • 2.1 Linux内核源代码
    • 2.2 查看进程状态
    • 2.3 D磁盘休眠状态(Disk sleep)
    • 2.4 T停止状态(stopped)
  • 三、僵尸进程
    • 3.1 僵尸进程危害总结
  • 四、孤儿进程
  • 五、结语

一、进程状态概述

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

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

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

  • 运行状态:进程正在执行指令,占用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内核源代码

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
	"R (running)", /* 0 */
	"S (sleeping)", /* 1 */
	"D (disk sleep)", /* 2 */
	"T (stopped)", /* 4 */
	"t (tracing stop)", /* 8 */
	"X (dead)", /* 16 */
	"Z (zombie)", /* 32 */
};
  • 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:所有的进程只对它的“儿子”,即子进程负责,不会对它的孙子进程负责,因为代码中只有创建子进程的逻辑,并没有创建孙子进程的逻辑,所以并不是不想让爷爷进程来回收孙子进程的资源,是因为爷爷进程没有这个本事,而操作系统会直接从内核层面进行回收,所以当一个进程的父进程结束后,会把该进程交给操作系统,让操作系统来充当它的父进程。

五、结语

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/904236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【线性代数-3Blue1Brown】- 4 矩阵乘法与线性变换复合

飞书原文档: https://fvcs2dhq8qs.feishu.cn/docx/QRfNdiRAcoaSn0xLSIrcv1W0n9f?fromfrom_copylink

【教程】华南理工大学校园网登录抓包和协议模拟

每次手动登录特别麻烦,而且时不时断一下,因此搞个脚本让它定时监测、断开重连比较方便。这里不讲这个脚本怎么写,只记录一下登录时的抓包内容。 蒜了,直接上解析吧,也不复杂,相信大家一目了然。 目录 抓包…

Linux命令200例:tail用来显示文件的末尾内容(常用)

🏆作者简介,黑夜开发者,全栈领域新星创作者✌。CSDN专家博主,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 &…

Java开发面试题 | 2023

Java基础 接口和抽象类的区别?Java动态代理HashMap 底层实现及put元素的具体过程currenthashmap底层实现原理?map可以放null值吗,currenthashmap为什么不能放null值synchronze和reetrantlock区别?怎样停止一个运行中的线程&#…

WPS-RCE

版本&#xff1a; WPS Office 2023 个人版 < 11.1.0.12313 WPS Office 2019 企业版 < 11.8.2.12085 原理&#xff1a; Office 中的 WebExtension&#xff08;通常称为 Office 插件或 Office 应用程序&#xff09;是一种用于扩展 Microsoft Office 功能的技术。Office …

Nacos 无法注册服务

情况描述 Nacos服务搭建完成。 引入了Nacos的依赖。 <!-- SpringCloud Alibaba 服务管理 --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependen…

Linux之套接字UDP实现网络通信

Linux之套接字UDP实现网络通信 文章目录 Linux之套接字UDP实现网络通信1.引言2.具体实现2.1需要知道的套接字接口1.socket()2.bind()3.recvfrom()4.sendto() 2.2服务器端server.hpp2.3服务器端server.cc2.4客户端Client.cc 1.引言 ​ 套接字(Socket)是计算机网络中实现网络通信…

Java-类与对象(下)

访问限定符 类可以将数据以及封装数据的方法结合在一起&#xff0c;更符合人类对事物的认知&#xff0c;而访问权限用来控制方法或者字段能否直接在类外使用。 这里目前主要掌握private&#xff0c;default和public public&#xff1a;可以理解为一个人的外貌特征&#xff0c;…

金狮金狮鹏保宝点盾云等加密视频翻录录屏教程

这款软件采用独家方法特殊方式打开录屏工具&#xff0c;理论上通杀所有的检测进程的加密视频播放器的检测&#xff0c;不被检测。 使用方法很简单&#xff1a; 点击 打开录屏工具&#xff0c;就可以以特殊方式加载录屏工具。 然后就可以开始录屏了。 下载&#xff1a;spjm.lan…

在线HmacSHA1加密工具--在线获取哈希值又称摘要

具体请前往&#xff1a; 在线计算HmacSha1工具

0102垂直分片-jdbc-shardingsphere

1 前言 垂直分片分为垂直分库于垂直分表&#xff0c;但是实际应用中数据库表会根据业务需要提前做好规划&#xff0c;垂直分表的情况很少使用。但是随着业务增长&#xff0c;某些重点业务数据库压力增大&#xff0c;可能考虑需要考虑垂直分库的情况。 垂直分库&#xff0c;简…

22-组件通信

一. 什么是组件通信 组件通信&#xff0c;就是指 组件与组件 之间的数据传递 1. 组件的数据是独立的&#xff0c;无法直接访问其他组件的数据。 2. 想用其他组件的数据 -> 组件通信 二. 不同的组件关系 和 组件通信方案分类 组件关系分类: 1. 父子关系 2. 非父子关系 组件…

【Unity】制作一个简单的菜单栏页面并实现其功能

这是一个简单的菜单页面制作&#xff0c;接下来我们将制作一个完整的菜单页面&#xff0c;并且通过一定的代码去实现它对应的效果。这个主要的功能就是我们在游戏中如果想暂停一下或者重新开始&#xff0c;那么就要用到我们这个功能。接下来我们将实现在游戏中按ESC退出键可以调…

第6步---MySQL的控制流语句和窗口函数

第6步---MySQL的控制流语句和窗口函数 1.IF关键字 -- 控制流语句 SELECT IF(5>3,大于,小于);-- 会单独生成一列的 SELECT *,IF(score >90 , 优秀, 一般) 等级 FROM stu_score;-- IFNULL(expr1,expr2) SELECT id,name ,IFNULL(salary,0),dept_id FROM emp4;-- ISNULL() …

【C语言学习】数组

定义数组 <类型>变量名称[元素变量] 元素数量必须是整数 int a[10]; double b[20]; scanf("%d",&a[i]);//输入数组数组 数组是一种容器&#xff0c;特点为其中所有元素具有相同的数据类型&#xff1b; 一旦创建&#xff0c;不能改变大小&#xff1b; 数…

【C语言】什么是结构体内存对齐?结构体的大小怎么计算?

目录 1.结构体内存对齐 对偏移量的理解&#xff1a;​ 2.结构体的大小计算 2.1结构体中只有普通的数据类型的大小计算 2.2 结构体中有嵌套的结构体的大小计算 3.修改默认对齐数 4.为什么存在内存对齐? 这篇文章主要介绍结构体内存对齐和如何计算大小。 在学习结构体内存…

个性化定制界面 VS 极简版原装界面:你更喜欢哪一个?为什么?

文章目录 每日一句正能量前言自己的喜好使用这种界面的原因这种界面对你的影响后记 每日一句正能量 不管昨天、今天、明天&#xff0c;能豁然开朗就是最美好的一天。 前言 个性化定制界面和极简版原装界面&#xff0c;哪一个你用起来更加顺手呢&#xff0c;相比之下你更喜欢哪一…

空洞卷积网络实现

代码&#xff1a; import torch.nn as nn import numpy as npfrom matplotlib import pyplot as plt import time #from utils import get_accur,load_data,trainimport torchvision from torchvision import transforms from torch.utils.data import DataLoader import torch…

硬编码基础一(经典定长指令,寄存器相关)

硬编码基础一&#xff08;定长指令&#xff09; push/pop 通用寄存器 50~57是push8个32位通用寄存器 58~5f是pop8个32位通用寄存器 inc/dec 通用寄存器 40~47是inc8个32位通用寄存器 47~4f是dec8个32位通用寄存器 八位通用寄存器的立即数赋值 b0~b3 {立即数} 是低八位(…

蓝蓝设计-专业UI设计公司-界面设计作品

数慧时空&#xff08;全称&#xff1a;北京数慧时空信息技术有限公司&#xff09;是以空间信息技术为核心、国内领先的国土资源行业解决方案提供商&#xff0c;具有业务咨询、自主软件研发、数据加工和信息服务的全业务综合服务能力&#xff0c;是国土资源部最重要的信息化合作…