【Linux】详解进程状态之僵尸进程——孤儿进程

news2024/9/27 9:21:31

目录

🌞专栏导读

🌛什么是进程

⭐什么是PCB? 

🌛查看进程 

🌛如何通过系统调用查看进程PID

🌛fork

🌞认识进程状态

🌛查看进程状态 

🌛R状态

 ⭐例如:

🌛S状态 

🌛D状态 

🌛T状态

🌛t状态:

🌛X状态 

🌛Z状态

⭐僵尸进程

⭐僵尸进程的危害 

🌛孤儿进程


🌞专栏导读

🌟作者简介:日出等日落,在读本科生一枚,致力于 C/C++、Linux 学习。

🌟本文收录于 Linux系列,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,持续更新!

🌟相关专栏推荐:C语言系列 、Linux系列 、数据结构与算法

🌛什么是进程

有小伙伴就会问了,什么是进程呢?

进程=内核关于进程的相关数据结构+当前代码的内容和数据

什么是进程?


早期的计算机一次只能执行一个程序,这种程序完全控制系统,并且访问所有系统资源。到了现代,计算机系统允许加载多个程序到内存,以便于并发执行。这就要求操作系统对各种程序提供更严格的控制和更好地划分和规划。这些需求引发了进程概念的产生,大白话来说,进程就是正在执行的程序,是现代分时操作系统的工作单元。
操作系统的复杂程度决定它可以为用户带来更好地体验感。虽然它主要关注的是执行用户程序,但是它也要顾及各种系统任务。因此系统会由一组进程组成,操作系统进程和用户进程;操作系统进程执行系统代码,而用户进程执行用户代码。
通过 CPU 的多路复用,所有这些进程可以并发执行。通过在进程之间切换 CPU,操作系统能使计算机更为高效。

前面说,进程是执行的程序,这是一种非正式的说法。进程不只是程序代码(文本段或代码段),通常还包含以下内容:

  1. 当前活动,如程序计数器的值和处理器寄存器的内容等。
  2. 进程堆栈(包括临时数据,如函数参数、返回地址和局部变量)和数据段(包括全局变量)。
  3. 堆,这是在进程运行时动态分配的内存。

⭐什么是PCB? 

从操作系统理解进程概念-------先描述,后组织
为了使参与并发执行的程序能独立的运行,必须为之配置一个专门的数据结构-----task_struct,称为进程控制块(PCB进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。

系统利用PCB来描述进程的基本情况和运行状态,进而控制和管理进程(也就是组织)进程。 相应地,由程序段、相关数据段和PCB三部分构成了进程映像(进程实体)。所谓创建进程,实质上是创建进程映像中的PCB;而撤销进程,实质上是撤销进程的PCB。值得注意的是,进程映像是静态的,进程则是动态的。
进程的唯一标志—PCB
综上所述,进程的定义大体分为以下几点

  • 进程是程序的一次执行
  • 进程是一个程序及其数据在处理机上顺序执行时所发生的活动
  • 进程是具有独立功能的程序在一个数据集合上运行过程,它是系统进行资源分配和调度的一个独立单位

进程的特征

  • 进程是由多程序的并发执行而引出的,它和程序是两个截然不同的概念。进程的基本特征是对比单个程序的顺序执行提出的,也是对进程管理提出的基本要求。

动态性:进程是程序的一次执行,它有着创建、活动、暂停、终止等过程,具有一定的生命周期,是动态地产生、变化和消亡的。动态性是进程最基本的特征。
并发性:指多个进程实体,同存于内存中,能在一段时间内同时运行,并发性是进程的重要特征,同时也是操作系统的重要特征。引入进程的目的就是为了使程序能与其他进程 的程序并发执行,以提高资源利用率。
独立性:指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位。凡未建立PCB的程序都不能作为一个独立的单位参与运行。
异步性:由于进程的相互制约,使进程具有执行的间断性,即进程按各自独立的、 不可预知的速度向前推进。异步性会导致执行结果的不可再现性,为此,在操作系统中必须 配置相应的进程同步机制。
结构性:每个进程都配置一个PCB对其进行描述。从结构上看,进程实体是由程序段、数据段和进程控制段三部分组成的。
进程的状态和转换

task_ struct内容分类

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]
  • IO状态信息: 包括显示的I/O请求,分配给进程的IO设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息

🌛查看进程 

#include<stdio.h>

int main()
{
  while(1)
  {
    printf("我是一个进程\n");
    sleep(1);
  }
  return 0;
}
  • 这是一个简单的进程(myprocess)
ps axj | head -1 && ps axj | grep myprocess | grep -v grep
  • 这是查看进程的指令!

🌛如何通过系统调用查看进程PID

我们还可以用系统调用getpid()获取当前进程的pidgetppid()获取当前进程的父进程的PID

首先通过man手册查询这两个函数如何使用:

$ man getpid

运行截图: 

 注意,返回值类型未pid_t其实就是size_t。通过一下代码进行测试:

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

int main()
{
  while(1)
  {
    printf("我是一个进程,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
    sleep(1);
  }
  return 0;
}

运行截图: 

🌛fork

首先我们通过man手册认识一下fork

$ man fork

运行截图: 

 简单说明就是fork用来创建子进程,在父进程中,fork的返回值为子进程的PID;在子进程中返回值为0。

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

int main()
{
  printf("fork调用之前的内容.....\n");
  fork();
  printf("fork调用之后的内容.....\n");

  return 0;
}

运行截图: 

我们可以看出这里fork调用之后的内容打印了两遍。原因是当调用fork之后,子进程被创建,父进程与子进程同时在运行,于是父进程打印了一遍fork调用后的内容,子进程也打印了一遍fork调用后的内容。

fork的功能很强大,我们一般需要与if配合使用进行分流。还记得上面提到的fork的返回值吗?子进程返回值为0,父进程返回值为子进程PID,可以此作为分流的依据。例如:

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

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

  if(ret==0)
  {
    // 子进程
    while(1)
    {
       printf("我是子进程,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
       sleep(1);
    }
  }
  else 
  {
    // 父进程
    while(1)
    {
       printf("我是父进程,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
       sleep(1);
    }
  }

  return 0;
}

运行截图:

此刻父子进程都在运行。那么问题来了——

  • 请问为什么此时ifelse竟然能够同时执行?也就是fork为什么会有两个返回值

 进程间是互相独立的

例如qq与微信同时运行,两个并无关联,互不影响。

我们知道进程=内核数据结构(PCB)+代码和数据。当子进程创建时,操作系统会为子进程创建一个PCB记录子进程的状态信息等。同时子进程会与父进程共同使用一份代码和数据,若有任意一个执行流(只父子进程)修改数据时,操作系统会为该进程将代码和数据拷贝一份,再进行修改,此动作我们称之为写时拷贝,写时拷贝同样为非常重要的知识。

最后,因为进程具有独立性,同样父子进程也具有独立性,且由于写时拷贝的存在,父进程中调用fork会返回子进程的PID,所以else执行了,这是父进程的行为;子进程中会fork会返回0,所以if执行了,这是子进程的行为。父子进程互不影响

🌞认识进程状态

进程在其生命周期内,由于系统中各进程之间的相互制约关系及系统的运行环境的变化,使得进程的状态也在不断地发生变化(一个进程会经历若干种不同状态)。通常进程有以下五种状态,前三种是进程的基本状态。

进程转换图

  • 运行状态:进程正在处理机上运行。在单处理机环境下,每一时刻最多只有一个进程处于运行状态。
  • 就绪状态:进程已处于准备运行的状态,即进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行。
  • 阻塞状态:又称等待状态:进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲,该进程也不能运行。
  • 创建状态:进程正在被创建,尚未转到就绪状态。创建进程通常需要多个步骤:首先申请一个空白的PCB,并向PCB中填写一些控制和管理进程的信息;然后由系统为该进程分 配运行时所必需的资源;最后把该进程转入到就绪状态。
  • 结束状态:进程正从系统中消失,这可能是进程正常结束或其他原因中断退出运行。当进程需要结束运行时,系统首先必须置该进程为结束状态,然后再进一步处理资源释放和 回收等工作。

Linux中进程状态一般有:

  • R(运行状态):并不意外着真正的在运行(指正在被CPU调度);
  • S(休眠状态):进程在等待获取某种资源,此状态还被称为可中断休眠;
  • D(磁盘休眠状态):在这个状态的进程也是在休眠,但是不可被中断,因此又称过该状态为不可中断休眠;
  • T(暂停状态):可以通过发送 SIGSTOP 信号给进程来停止进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X(死亡状态):这个状态只是一个返回状态,你不会在任务列表里看到这个状态;
  • Z(僵尸状态):当一个子进程没有被父进程“回收”,该进程就会处于僵尸状态;

 

🌛查看进程状态 

指令:

ps axj | head -n1 && ps axj | grep myprocess | grep -v grep

🌛R状态

当你在电脑上同时运行很多程序,例如你敲代码的时候,还听着某个软件播放的歌曲,或者在浏览器之间来回切换。请问此时这些所有的应用都在CPU运行吗?

答案是,并不是这样的。

在CPU进行工作的时候,会存在一个进程运行的队列。队列维护的内容是一个个task_struct结构体的指针。在该队列中维护的进程都处于R状态,且等着被CPU所调度。

 ⭐例如:

根据上图,问题来了,为什么在该程序执行时,并没有看到所谓的R状态呢?


原因是由于CPU运算速度太快了,我们基本很难看到R状态。该进程死循环的在屏幕上打印。我们都知道此时的屏幕是一种外设,而CPU的计算速度相比较外设的访问速度根本不在一个量级。所以,该进程死循环的在屏幕上打印内容,有99.9%的时间都在访问外设,剩下的时间是CPU在做计算。在进程访问外设的时候,CPU并不会傻傻的原地等待,而是转头却做别的事,当该进程访问外设成功后,CPU再对它进行调度。

那么有什么办法等看到R状态呢?我们将上面的代码略作修改:

#include<stdio.h>
#include<unistd.h>

int main()
{
  while(1)
  {
    //printf("hello myprocess\n");
  }
  return 0;
}

 运行截图:

 如上图所示,当我们不再访问外设,而是只不停地做重复的运算,此时CPU会一直被调度,就能看到R状态了。

🌛S状态 

S状态称为休眠状态。休眠状态本质是一种阻塞。

阻塞:进程因为等待某种资源就绪而表现出的不推进的状态。

例如,当一个进程运行到一半,需要从磁盘上获取很大的一块数据,那么就要花费较久的时间。此时OS的处理方式是,让该进程继续等待它要的数据,但是要求你不能在等待资源的时候还占用着CPU,于是该进程就被OS安排到某个地方进行等待,这时该进程就处于S状态。


运行截图

如上图所示,当进程等待用户从键盘上输入的数据时,它就处于睡眠状态。 

🌛D状态 

D状态也是一种休眠状态,但是它又有个名字叫做磁盘休眠状态或者不可中断休眠。那么如何看待S状态与D状态的区别呢?

🌛T状态

T状态称为停止状态,就是让某个进程暂停一下。

1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6   int count = 0;
  7    while(1)
  8    {
  9      //printf("hello myprocess\n"); 
 10      printf("我再运行吗??%d\n",count++);
 11      sleep(1);                                           
 12    }
 13    return 0;
 14 }

当程序开始运行后,此时向进程发送暂停的信号:

$ kill -18 (进程PID)

运行截图: 

此外,我们还可以发送继续的信号让该进程继续执行:

$ kill -18 (进程PID)

 运行截图:

 注意:

进程继续在运行了。但是我们发现有一个地方好像和之前不一样了,S后面是不是一直有一个+号来着?我们也不知道+是干嘛的,只知道他现在好像消失了。

“+” 代表在前台运行,没有”+“表示在后台运行;


之前我们在终止一个程序时,习惯使用Ctrl + c ,但是现在好像对于后台在运行的进程失效了,此时我们需要掌握一条新的指令来”杀掉“进程:

$ kill -9 (进程PID)

🌛t状态:

 例如在调试时,我们设置了几个断点。当进程在该断点处停下来时,该进程就处于暂停状态。

 

 

可以看见我们在第十行打了断点,此时观看进程状态:

 我们在调试中运行一下:

观看运行状态:

🌛X状态 

  • X状态为死亡状态是一个瞬时状态不易观察,暂且认为它不重要;

🌛Z状态

  • Z状态被称为僵尸状态。顾名思义,一个进程死了(退出了)但没有”收尸“,就成了”僵尸“。具体一点,当一个进程退出时如果它的父进程没有读取到该进程退出时返回的退出状态码,该进程就会变成僵尸进程。

⭐僵尸进程

#include<stdio.h> 
#include<unistd.h>

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

  if(id == 0)
  {
    while(1)
    {
      printf("我是子进程,我在运行,pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);
    }
  }
  else if(id > 0) 
  {
    while(1)
    {
      printf("我是父进程,我在运行,pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);
    }
  }
  return 0;
}

当我们运行程序后,能看到程序正常的在运行; 

 此时当我们执行指令将子进程”杀“掉,子进程就会变成僵尸进程;

$ kill -9 (子进程PID)

其中我们能看到一个英文单词——defunct就是僵尸的意思。 

⭐僵尸进程的危害 

  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(即PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护
  • 一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费。因为数据结构对象本身就要占用内存。

🌛孤儿进程

所谓孤儿进程,故名思义,和现实生活中的孤儿有点类似,当一个进程的父进程结束时,但是它自己还没有结束,那么这个进程将会成为孤儿进程。

运行该程序,我们使用kill命令”杀“掉父进程,此时再来查看进程信息:

如上图所示,子进程发生了两个变化。一是子进程的PPID,二是子进程变为在后台运行了。 

当子进程的父进程挂掉之后,子进程会被1号进程领养。该进程也被称为孤儿进程。

  • 那么为什么要进行领养呢?

原因是孤儿进程会被init进程(1号进程)的进程收养,当然在子进程结束时也会由init进程完成对它的状态收集工作,因此一般来说,孤儿进程并不会有什么危害.

 

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

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

相关文章

C 语言的 ctype.h 头文件

C 语言的 ctype.h 头文件包含了很多字符函数的函数原型, 可以专门用来处理一个字符, 这些函数都以一个字符作为实参. ctype.h 中的字符测试函数如表所示: 这些测试函数返回 0 或 1, 即 false 或 true. ctype.h 中的字符映射函数如表所示: 字符测试函数不会修改原始实参, 只会…

YOLOV5改进:更换为MPDIOU,实现有效涨点!

1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。 2.提供更加详细的改进方法,如将注意力机制添加到网络的不同位置,便于做实验,也可以当做论文的创新点。 2.涨点效果:更换为MPDIOU,实现有效涨点! 目录…

C++代码生成静态LIB链接库及其调用方法

1、在进行C代码移植时可将CPP文件封装为静态lib链接库&#xff0c;本文章讲述了如何将cpp文件封装为lib文件。 2、假设有文件a.cpp、a.h、b.cpp、b.h以及main.cpp&#xff0c;假设main调用了b&#xff0c;b调用了a。现在需要将a.cpp以及b.cpp封装为a.lib以及b.lib。 3、在VS2…

Java8中forEach()里使用return的效果

先总结&#xff1a;使用forEach()处理集合时不能使用break和continue这两个方法&#xff0c;可以使用无返回值的return跳出此次循环&#xff0c;效果同标准for循环的continue。 首先&#xff0c;forEach()先对入参判空&#xff0c;然后使用增强for循环调用action.accept(t)&am…

VGG16模型详解

VGG16模型详解 0、VGG16介绍 VGG16是一种深度卷积神经网络&#xff0c;由牛津大学的研究团队于2014年开发。 VGG16在2014年的ImageNet Large Scale Visual Recognition Challenge (ILSVRC) 竞赛中取得了显著的成绩。它在图像分类任务中获得了当年的第二名&#xff0c;其准确…

【Java可执行命令】(二十一)线程快照生成工具 jstack:帮助开发人员分析和排查线程相关问题(死锁、死循环、线程阻塞...)

Java可执行命令之jstack 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法格式3.2 使用步骤及技巧3.3 使用案例 4️⃣ 应用场景&#x1f33e; 总结 1️⃣ 概念 jstack 命令是 Java Development Kit&#xff08;JDK&#xff09;中提供的一项诊断工具&#xff0c;用于生成Java虚拟…

震坤行工业超市旗下震坤行智能制造(苏州)有限公司开工奠基仪式圆满成功

震坤行工业超市旗下震坤行智能制造&#xff08;苏州&#xff09;有限公司开工奠基仪式圆满成功 2023年7月3日&#xff0c;震坤行工业超市于太仓港经济技术开发区举行了震坤行智能制造&#xff08;苏州&#xff09;有限公司项目奠基动工仪式。震坤行董事长兼CEO陈龙&#xff0c…

基于OFDM通信系统的低复杂度的资源分配算法matlab性能仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 .......................................................................%子载波分配[~,po…

Grafana技术文档-概念-《十分钟扫盲》

Grafana官网链接 Grafana: The open observability platform | Grafana Labs 基本概念 Grafana是一个开源的度量分析和可视化套件&#xff0c;常用于对大量数据进行实时分析和可视化。以下是Grafana的基本概念&#xff1a; 数据源&#xff08;Data Source&#xff09;&#…

idea+gradle阅读spring5.2.9源码之源码构建报错解决方案

注意 1、先确保gradle版本和spring、jdk版本对应 本文:gradle:5.6.4/spring 5.2.9/jdk1.8&#xff08;gradle和jdk都要先安装好&#xff0c;gradle还要配置好本地资源文件路径&#xff09; 2、原来项目乱了的话&#xff0c;先重新导入下载的源码项目 3、进入源码所在根目录&…

【iOS】autoreleasepool

来说一下最近在了解的autoreleasepool吧&#xff0c;我们可能平时书写过许多脑残代码&#xff0c;其有很多的缺陷但是我们可能当时学的比较浅就也不太了解&#xff0c;就像下面这样的&#xff1a; for (int i 0; i < 1000000; i) {NSNumber *num [NSNumber numberWithInt…

【前端 | CSS】aligin-items与aligin-content的区别

align-items 描述 CSS align-items 属性将所有直接子节点上的 align-self 值设置为一个组。align-self 属性设置项目在其包含块中在交叉轴方向上的对齐方式 align-items是针对每一个子项起作用&#xff0c;它的基本单位是每一个子项&#xff0c;在所有情况下都有效果&…

【uniapp】原生子窗体subNvue的使用与踩坑

需求 最近接到个需求, 需要在video组件上弹出弹窗, 也就是覆盖video这个原生组件 未播放时, 弹窗可以覆盖, 但是当video播放时, 写的弹窗就覆盖不了了 因为video是原生组件, 层级非常高, 普通标签是覆盖不了的, map标签同理 覆盖原生组件, 官方给出解决办法一. 使用cover-view…

文件传输软件常见问题解决办法大全

文件传输软件是我们工作中不可缺少的一种工具&#xff0c;它可以帮助我们快速、安全、稳定地传输各种文件&#xff0c;如文档、图片、视频等。但是在使用文件传输软件的过程中&#xff0c;我们也可能会遇到一些问题&#xff0c;影响我们的工作效率和传输质量。那么&#xff0c;…

【陈老板赠书活动 - 10期】- 【Python之光:Python编程入门与实战】

陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;赠书活动专栏&#xff08;为大家争取的福利&#xff0c;免费送书&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。讲一些我刚进公司的学…

【ARM Cache 系列文章 9 番外篇 -- ARMv9 系列 Core 介绍】

文章目录 ARMv9 系列CoreARM Cortex-A510 介绍ARM Cortex-A715ARM Cortex-A720 ARMv9 系列Core 2021年5月Arm公布了其最新3款CPU和3款GPU核心设计&#xff0c;三款新CPU分别是旗舰核心Cortex-X2、高性能核心Cortex-A710、高能效核心Cortex-A510 CPU&#xff0c;三款新GPU核心则…

【Linux】深入探索Linux信号

目录 写在前面的话 什么是信号 生活中的信号 Linux下的信号 Linux常见信号 Core核心转储 信号如何产生 键盘组合键 1.如何理解信号被进程保存 2.如何理解信号发送的本质 通过系统调用向进程发送信号 kill() 手动实现kill指令 raise() abort()[非系统调用…

Django ORM 框架中的表关系,你真的弄懂了吗?

Django ORM 框架中的表关系 为了说清楚问题&#xff0c;我们设计一个 crm 系统&#xff0c;包含五张表&#xff1a; 1.tb_student 学生表 2.tb_student_detail 学生详情表 3.tb_salesman 课程顾问表 4.tb_course 课程表 5.tb_entry 报名表 表关系和字段如下图&#xff1a…

String 类的运用

目录 1.字符串构造 2.String对象的比较 2.1比较是否引用同一个对象 2. 2boolean equals(Object anObject) 2.3int compareTo(String s) 方法: 按照字典序进行比较 2.4int compareToIgnoreCase(String str) 3.字符串查找 4.2大小写转换 4.3字符串转数组 4.4 格式化 5.字…

java 企业工程管理系统软件源码 自主研发 工程行业适用 em

​ 工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#…