【Linux学习】进程概念(上)

时间:2022-11-10 16:52:09

????作者:一只大喵咪1201
????专栏:《Linux学习》
????格言:你只管努力,剩下的交给时间!
【Linux学习】进程概念(上)

????冯诺依曼体系结构

我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。

【Linux学习】进程概念(上)
上图是冯诺依曼体系结构示意图。

  • 输入设备:键盘,鼠标,扫描仪,写板等等。
  • 输出设备:显示器,声卡,打印机等等。
  • *处理器(CPU):运算器 + 控制器 + 其他(寄存器)。
  • 存储器:就是内存,磁盘等硬件是存,并不属于存储器,而属于外设(输入输出设备统称外设)。

不同部分的材料不同,性能也不同,其中,

  • 访问速度是:CPU >> 储存器 >> 外设。

这是的关系是远大于,它们的速度并不是在一个量级的,比如CPU是纳秒级别的,存储器是微秒级别的,而外设是毫秒级别的。

【Linux学习】进程概念(上)
当CPU需要一个数据,这个数据在外设和储存器中都有,如果从外设中拿需要1毫秒,从存储器中拿需要1微秒,CPU会选择从存储器中拿这个数据。

当CPU需要传出一个数据,如果直接给外设的话,也是需要1毫秒,而给存储器的话只要1微秒,CPU同样会选择将数据给存储器,然后存储器怎么处理这个数据CPU就不再管了。

可以看到,CPU直接和储存器进行数据交互,相比于直接和外设进行数据交互能够节省很多的时间。

  • 为了提高整机的工作效率,CPU不会和外设直接打交道,而是通过存储器来实现目的。

所以,存储器就负责从外设读取数据来交给CPU,也负责将CPU写入的数据再写入外设。

  • CPU其实是很笨的,只能被动接受别人的指令,别人的数据,执行别人的命令,从而来计算别人的数据。
  • 但是CPU的执行速度很快。

CPU接收到的别人的指示就是我们写的代码,我们的代码在写好后是保存在磁盘上的,经过编译链接后会形成.exe可执行程序。

只有可执行程序,也就是二进制机器码从磁盘上加载到存储器上,CPU才能开始执行。CPU它有自己的指令集,也就是有自己的语言,这些二进制的机器码是专门转化过来的,是翻译成了CPU可以看懂的指令集。

【Linux学习】进程概念(上)
上图中,假设张三要给李四发送一句你好,不考虑中间网络的具体工作情况,只考虑俩台电脑的数据流向:

  • 张三电脑的数据流向:
  • 1.张三从键盘上输入你好,此时键盘这个外设上就有了数据。
  • 2.外设上的数据传递给了存储器,存储器就让CPU来处理这些数据。
  • 3.CPU从存储器上读取数据并且进行处理,然后再将处理后的数据写到存储器上。
  • 4.存储器再将数据写到网卡,网卡也是一个外设。
  • 李四电脑的数据流向:
  • 1.李四电脑的网卡外设上受到了数据。
  • 2.外设上的数据传送给了存储器,存储器让CPU来处理这些数据。
  • 3.CPU从存储器上读取数据并且进行处理,然后再将处理后的数据写到存储器上。
  • 4.存储器再将数据写到显示器这个外设上。

从张三输入你好开始,数据要经过上诉整个过程的流向,李四的电脑屏幕上才会显示出你好俩个字。

结论:

  1. 在冯诺依曼体系下,CPU不会和外设直接打交道,只会和存储器直接打交道。
  2. 冯诺依曼体系的目的是为了提高整机的工作效率。
  3. CPU要执行代码,只能从存储器中读取,这是体系结构规定的。

????操作系统(OS)

  • 操作系统的定义:操作系统是一个进行软硬件资源管理的软件。

由于CPU不和外设直接打交道,只和存储器直接打交道,所以存储器注定会很忙。比如,要准备读取哪个外设的数据来让CPU执行,又有哪部分代码需要CPU来执行,CPU处理后的结果在什么时候写到外设,等等。

由于任务这么多,又这么繁杂,还需要合理的安排,而存储器就是用来存放数据的,这里的数据范围很广。所以就需要一个专业的人来管理这些数据,进行合理的安排。而这个人就是操作系统,操作系统在整个体系中就是管理者。

  • 操作系统存在的意义:通过合理管理软硬件资源,为用户提供良好的执行环境。

操作系统是一个管理者,管理的内容本质上就是各种数据。现实生活中也是如此,就比如我们在学校,校长是最大的管理者,校长是通过我们的学号,姓名,电话,成绩等等信息来管理我们的,虽然我们几乎没有见过校长的面,但是确确实实校长是通过我们的各种数据来管理我们的。

而校长获得我们这些数据是通过辅导员,班长等各种方式获得的,这些中间的是执行者。

结论: 管理的本质是在管理数据。

【Linux学习】进程概念(上)
操作系统这个管理者管理的内容主要有四大块,进程管理,文件管理,内存管理,驱动管理。

而它管理这些软硬件资源是通过管理数据来实现的,硬件的数据又是通过驱动程序这个执行者来获取到的。

拿硬件管理来举例:

【Linux学习】进程概念(上)
操作系统将使用结构体对象将这些硬件管理起来,结构体中放的就是硬件的各种属性信息,如上图所示。

【Linux学习】进程概念(上)
为了方便管理这些数据,将结构体对象使用链表管理起来。

软件资源也是类似的,只是结构体名为tast_struct,具体内容在后面的进程中本喵会详细讲解。

结论: 系统在管理资源的时候,都会先描述,再组织。

对于Linux操作系统,由于是用C语言写的,所以描述就是用struct结构体来记录资源的属性,然后用链表或者其他高效的数据结构组织起来,方便管理,一般而言,都是用的链表结构。

⚽软硬件体系结构

用户是位于操作系统之上的,用户的所有需求都是操作系统来完成的,如果用户直接与操作系统进行交互,对于操作系统来说不安全,如果用户在操作系统中乱操作,会导致系统崩溃。对于用户来说,需要对操作系统非常了解才能够直接操作它,成本会非常高,而且也不便于操作。

于是操作系统便提供了一些调用接口来供用户使用,这些接口和我们平时调用的函数是一个意思,只是此时是系统接口,操作的是系统。

  • 系统调用:系统提供的接口称为系统调用接口,俗称系统调用。

【Linux学习】进程概念(上)
上图是计算机的软硬件体系结构示意图。

用户可以通过shell指令来进行系统调用,比如ls指令,在屏幕上打印出当前目录下的文件。

还有一些库函数,比如printf,cout,sanf,cin等等,都是通过系统调用来与硬件进行交互。

系统调用不仅仅只有硬件,还包括许多软件,比如getpid()就是一个系统结构,它是用来查看系统中进程的pid的,也就是用来查看内存中程序的标识的。

系统调用和库函数:

  • 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
  • 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。

????进程

进程的概念:正在执行的程序。

其实这个概念不是很容易理解,下面本喵画图给大家解释。

【Linux学习】进程概念(上)
一段程序,在经过编译链接处理后会生二进制机器码可执行程序.exe,此时可执行程序是存放在磁盘外设上的。

当程序要运行的时候,可执行程序会从硬盘加载到内存上,其实就是将磁盘上的内容复制到内存中,如上图的蓝色小方框。

在可执行程序加载到内存中时,操作系统就会创建一个结构体对象task_struct,来描述可执行程序的各种信息,并且将这个结构体对象放入操作系统维护的数据结构中。

当可执行程序加载到内存中,建立对应的结构体对象并且放到数据结构中后,此时就变成了一个进程。

所以说,进程 = 内核数据结构 + 对应的可执行程序代码。

当有多份可执行程序从磁盘中加载到内存中后,操作系统就会创建多个结构体对象,并且把它们都放入数据结构中来管理。

当操作系统根据结构体对象中的某各key值将结构体对象挑选出来让CPU执行的时候,CPU会根据结构体对象中的内容,找到对应内存中的可执行程序取去执行。

结论: 操作系统维护的不是二进制可执行代码本身,而是它对应的结构体对象。

????进程控制块(PCB)

概念:用来描述进程信息的数据结构,可以理解为进程属性的集合。

上面本喵所说的,用来描述进程各种属性进行的结构体,在Linux操作系统下结构体标签是task_struct,所创建的结构体对象所在的数据结构叫做PCB。

结构体的代码形式如下:

struct task_struct
{
	//该进程的所有属性信息
	//该进程对应的代码地址
	struct task_struct* next;
};

各个结构体对象再以下图的形式组织起来:
【Linux学习】进程概念(上)
这就是代码控制块,简称PCB。

tast_struct结构体中的内容大致有如下几类:

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

有兴趣的小伙伴可以查看一下Linux源码中task_struct结构体的定义,这里本喵就不给大家展示了。这些属性在后面本喵会讲解到一些,这里只是建立一个大概的认识。

结论:

  1. 软件程序在内存中也和硬件一样,同样是先描述再组织,形成一个PCB集合。
  2. 进程是动态的,所以它具有动态属性。

????进程的基本操作

⚽查看进程

方法一:

  • 指令:ps ajx
  • 功能:显示进程的简要信息,只有这条指令的话会显示很多进程的信息
  • 组合:使用管道(|)组合其他指令来查看指定进程的信息
  • ps ajx | grep 进程名

【Linux学习】进程概念(上)

运行一个上面代码对应的进程,来看它的进程信息。
【Linux学习】进程概念(上)

将SSH窗口复制一个,一个用来查看进程的运行如上图上边框,一个用来查看进程信息,如上图下边框。

红色框中执行的是指令,绿色框中是执行后的结果。有俩个进程信息被显示出来,一个是test我们要查看的,另一个是grep指令,因为要用到grep和管道配合过滤出我们要的进程,所以grep指令也是在运行的,也是一个进程。

  • 指令:ps ajx | head -1 && ps ajx | grep test
  • 功能:将多个指令组合在一起,显示出进程的信息
  • 解释:
  • ps ajx 是查看所有进程
  • head -1 是显示进程信息显示的第一行,也就是抬头
  • grep test 是表示过滤掉其他,只显示test

【Linux学习】进程概念(上)
组后和的指令结果是这样的,抬头中的内容在后面本喵会慢慢给大家介绍。

方法二:

还有一种方法来查看进程,这个只需要了解即可。

  • 指令:ls /proc
  • 功能:查看系统上当前运行的进程,proc是专门用来放进程的文件。

【Linux学习】进程概念(上)

我们使用第一种方法查看进程信息时,有一个PID值,如上图中绿色框中的数字,这个值是一个进程的唯一标识符,可以看到,在proc中同样有PID值为9158的进程。

注意: PID是一个进程的唯一标识符,是用来识别一个进程的。

⚽结束进程

方法一:

  • 在键盘上按Ctrl + C就结束了一个正在运行的进程

【Linux学习】进程概念(上)
此时正在运行的进程就结束掉了,只有在前台运行的进程才能使用这种方法结束。

方法二:

  • 指令 kill -9 进程PID
  • 功能:杀掉PID值对应的进程

【Linux学习】进程概念(上)
原本正在执行的进程,在执行完杀掉进程的指令以后,直接停止了,并且系统反馈killed,如上图中绿色框所示。

这种方法不仅可停止前台运行的进程,而且使用Ctrl + C不能停止的后台进程也可以停止。

前后台进程:

前台进程:

【Linux学习】进程概念(上)
在进程运行的过程中,输入命令行系统不做出任何反应,如上图红色框中所示。
【Linux学习】进程概念(上)
在查看进程时,它的状态信息S后有一个+号,+号表示这个进程是一个前台进程。

后台进程:

  • 指令:kill -19 进程PID
  • 功能:暂停一个进程
  • 指令:kill -18 进程PID
  • 功能:继续执行暂停的进程

【Linux学习】进程概念(上)
先使用正在运行的进程暂停,然后再继续运行。

【Linux学习】进程概念(上)
此时在进程运行的时候是可以输入命令行的,并且也会执行,如上图中的绿色框中所示。

此时再查看进程的信息,在状态属性中,S+变成了S,后面没有+号了,表示这是一个后台进程。

【Linux学习】进程概念(上)
后台进程使用Ctrl + C是无法停止的,只能使用kill -9 指令来停止。

⚽通过系统调用获取进程标识符

  • 系统调用接口:getpid()
  • 功能:获得当前进程的PID
  • 系统调用接口:getppid()
  • 功能:获取父进程的PID

【Linux学习】进程概念(上)
在代码中使用系统调用,来获得当前进程的PID和父进程的PID。

【Linux学习】进程概念(上)
运行进程时就会打印出当前进程的PID和父进程的PID。

  • PID值其实就是一个整型数据,使用%d打印即可

【Linux学习】进程概念(上)
将运行的进程结束掉,然后再运行,发现进程的PID值变了,但是父进程的PID值没有变。

当一个进程结束以后,操作系统就会将它的PCB杀掉,此时内存中就没有这个进程的信息了,原本PID标识的就是另一个运行的进程了。

当这个进程再次加载到内存中运行后,就会操作系统会创建新的PCB来维护它,所以它的PID值就变了。

【Linux学习】进程概念(上)
当前进程的PID值是23547,如上图中的绿色框,查看PID值为23547的进程信息,可以看到,它父进程的PID值是31869,而PID值为31869的进程是bash,也就是shell,如上图中的黄色框。

因为shell在系统启动后就开始运行,

  • 所以由命令行启动的进程都是bash进程的子进程,所以无论子进程的PID值怎么变化,它们的父进程的PID值都不会变化。

⚽通过系统调用创建子进程

  • 系统调用接口:fork()
  • 功能:在执行完fork以后,存在俩个进程,一个父进程一个子进程。

【Linux学习】进程概念(上)
通过man指令来查看fork系统调用的返回值。

  • 子进程创建成功,返回值有俩个:
    一个是子进程的PID,这个值给父进程,还有一个值是0,这个值给子进程。
  • 创建失败,返回-1给父进程。

和我们之前调用函数完全不同,我们之前任何函数的返回值只能是一个,fork的返回值居然有俩个,后面本喵会将其中的原理,现在只需要记住去使用它即可。

【Linux学习】进程概念(上)
在上图中的代码创建子进程,并且打印创建后父子进程各种的PID值和PPD值,还有各自进程的计数值。

【Linux学习】进程概念(上)
子进程和父进程都是1秒钟打印一次,如上图中红色框中所示,并且计数值也是在各自的循环中,可以看到,父进程和子进程是同时在执行的。

子进程的PID是29452,PPID是29451,而父进程的PID是29451,PPID是31869,如上图中的绿色框中所示。

子进程的PPID是父进程的PID,说明子进程是由父进程创建的。

  • fork之后的代码,父进程和子进程是共享的。

fork之后的代码,子进程和父进程是同时都执行的,但是由于fork后有俩个返回值,子进程的PID值给父进程,是一个大于0的数。将0给创建的子进程,所以可以根据返回值的不同,通过条件判断让子进程和父进程同时执行不同的代码。

????进程状态

⚽普遍的操作系统层面

站在操作系统角度,包括所有操作系统,程序被加载到内存,操作系统创建结构体对象形成PCB,成为进程以后,这个进程还有很多种状态,比如运行,新建,就绪,挂起,阻塞,等待,停止,挂机,死亡等等,进程的状态不同,意味着CPU执行它的情况也不同。

最重要的状态有三种,运行,阻塞,挂起,本喵这里也只讲这三种,其他的在涉及到的时候再说。

【Linux学习】进程概念(上)
这是一个宏观的操作系统示意图。

运行状态:

在内存中,操作系统会维护一个运行队列,代码结构如下:

struct runqueue
{
	task_struct* head;
	//其他属性
};

进程的PCB在需要运行的时候就会放进这个运行队列中,里面不仅有一个PCB结构体对象,CPU会按照运行队列中PCB的结构体对象task_struct中的信息找对应加载到内存中的二进制代码去执行。

  • 只要进程的PCB处于运行队列中,那么这个进程就是运行状态。

运行队列中不仅有一个PCB,会有很多个,毕竟CPU的执行速度很快,但是CPU只有一个,能执行的也只有一个进程,其余在运行队列中等待的进程仍然属于运行状态。就好比在跑4×100接力赛,虽然跑着的只有一个人,但是其余的三个人都处于比赛状态。

自行对应宏观示意图。

阻塞状态:

CPU只有一个,进程有很多个,处于运行状态的进程也有很多个。同样的,硬件相对于进程也少很多,比如你的电脑,键盘只有一个,显示器也只有一个。

所以当多个进程都需要使用到同一个硬件的时候,就会有很多进程在等待硬件就绪。

同样的,操作系统针对每个硬件都也维护了一个硬件的等待队列,代码结构如下:

struct dev_display
{
	task_struct* waitqueue;
	//其他属性
};

当一个处于运行状态的进程被执行到需要使用硬件的指令时,此时硬件也正在被其他进程使用,而且还有好几个进程在排队,此时这个被CPU执行的处于运行状态的进程就会被操作系统放入专门用来管理这个硬件的等待队列中,等待硬件就绪,而CPU去执行其他处于运行状态的进程。

  • 处于硬件等待队列中的进程,它的状态就是阻塞状态。

当硬件准备就绪后,操作系统就会将原本处于等待队列中的处于阻塞状态的进程重新放回到让CPU去执行的运行队列中,进程的状态从阻塞状态变为运行状态。

自行对应宏观示意图。

挂起状态:

内存相比于磁盘还是很小的,它的内存空间是有限的,而在硬件的等待队列中处于阻塞状态的进程,它在内存中不仅有PCB结构体对象task_struct,还有对应的从磁盘中加载进来的二进制代码。

当处于阻塞状态的进程数量很多时,内存就会被占据很多,此时在运行队列中处于运行状态的进程数量势必会受到限制,当需要执行的进程增多时,内存就会不足了,导致需要执行的进程无法加载到内存中。

【Linux学习】进程概念(上)

此时为了腾出更多的空间来执行进程,操作系统会将处于阻塞状态的PCB所以对应的二进制代码及阻塞之前执行产生的一些临时数据复制到磁盘中,并将内存中的这部分内容杀掉,而此时这个进程的状态就是挂起状态。

  • 处于阻塞状态的进程,在内存中仅有PCB对象task_struct,而没有对应代码,这个进程就处于挂起状态。

挂起状态并不是将这个进程在内存中释放了,因为PCB仍然处于硬件的等待队列中。

所以说,阻塞不一定挂起,但是挂起一定阻塞了。

结合进程的三个状态,我们可以得出一下结论:

  1. 一个CPU只有一个运行队列(struct runqueue)。
  2. 让一个进程进入队列,本质是让它PCB中的task_struct结构体对象进入队列。
  3. 进程PCB的中的结构体对象在运行队列中,该进程就处于运行状态,而不是这个进程正在被执行才是运行状态。
  4. 进程不仅会等待CPU资源,还会等待硬件资源,进程随时随地都有可能需要外设资源。
  5. 进程状态的不同,本质是PCB中的结构体对象task_struct处于的队列不同。

⚽具体到Linux操作系统层面

上面所说的进程状态是针对所有操作系统的,放在任何一个操作系统中都不会错,具体某一个操作系统中的进程状态肯定属于上面这些状态中的一种,只是具体表现不同罢了。

在Linux操作系统中一个进程的状态都放在一个指针数组中,来看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 ):

【Linux学习】进程概念(上)
这样一段代码,一直都在进行运算,也没有访问任何硬件资源。

【Linux学习】进程概念(上)
将这段代码编译链接后加载到内存中并且执行起来后,查看这个进程的状态,可以看到它的状态是R+,如上图中绿色框所示,表示正处于运行状态。

睡眠状态(S):

【Linux学习】进程概念(上)

在程序中加一个打印语句,此时这段代码就会使用到显示器这个外设资源,并且使用一个计数值来证明程序是在运行的。

【Linux学习】进程概念(上)
此时这个进程的状态是S+,也就是处于睡眠状态,如上图红色框。

这个程序中,需要CPU来运算的部分只有a = 1+1 ,以及cnt++,CPU的速度是非常快的,而printf访问的显示器外设相比较CPU运算所花费的时间就多很多,根本不是一个量级的。

  • 可以理解为,CPU运算所占整个进程运行的时间不到百分之1。

所以这个进程的绝大多数时间都在等待外设就绪,此时这个进程的PCB中的task_struct结构体对象大多数时间都处于显示器的等待队列中,也就是处于阻塞状态。

阻塞了进程就不再往下执行了,在Linux中,称这个状态为睡眠。

深度睡眠(D):

如果一个进程中,有大量的数据需要存放到磁盘上,这个进程的PCB同样会被操作系统放在磁盘的等待队列中,因为数据量非常大,磁盘的速读又很慢,所以PCB需要很长的时间来等待磁盘的应答信号。

当内存中又加载进来很多进程时,内存空间就会吃紧,此时操作系统为了维护正常运行,就会将一些长时间处于睡眠状态的进程杀掉,向磁盘传送大量数据的进程就会被杀掉。

当磁盘存储数据出现了问题,它就会向内存中原本的进程发送应答信号,但是此时这个进程已经被操作系统杀掉了,所以就无法接收信号也无法给出相应的指示。磁盘没有收到指示后会将这些数据放弃掉,如此一来就会导致数据丢失,如果是一些很重要的客户数据就会导致很大的损失。

  • 用户给可能被杀掉的进程一个免死金牌(设置一个标志位),此时操作系即使在内存吃紧的情况下也不会杀掉处于睡眠状态的进程。

被给与免死金牌,并且处于睡眠状态的进程,就被叫做深度睡眠状态。

深度睡眠状态的进程无论是使用Ctrl + C还是使用kill -9都无法杀死,只能等它自己醒来或者断电重启系统,所以本喵这里就不演示了,有兴趣的小伙伴可以自己去试试,但是很容易导致自己的系统崩溃。

暂停状态(T):

这个状态本喵在前面介绍前台进程和后台进程的时候涉及到过,这里本喵继续演示一下:

【Linux学习】进程概念(上)
仍然使用上面的程序,在程序执行的过程中,虽然这个进程的大部分状态处于睡眠状态,但是使用指令kill -19 后,这个进程仍然会变成停止状态,此时它就不再执行了,如上图绿色框中所示。进程状态由S+变成了T。

【Linux学习】进程概念(上)
使用kill -18指令后,将处于暂停状态的进程变成了睡眠状态,如上图中绿色框,变成了S,此时程序又开始执行了,虽然进程状态是睡眠,但是它是处于执行状态,而不是暂停状态。

追踪暂停状态(t):

将上面的程序重新编译,生成带有调试信息的Debug版本。

【Linux学习】进程概念(上)
在第8行处打一个断点,然后执行,此时在执行到第8行的时候就会停下来,此时来查看这个进程的状态:

【Linux学习】进程概念(上)
此时这个进程处于追踪停止状态,如上图中的红色框。当然此时gdb也在运行,而且是处于睡眠状态的,在等待用户的下一步指示。

  • 正在调试的进程,它的状态就是追踪暂停状态。

死亡状态(X):

当一个进程执行结束或者是被操作系统杀掉,它的task_struct结构体对象从PCB中删除,并且对应加载到磁盘上的二进制代码也被删除,此时这个进程就处于死亡状态。

  • 当一个进程所占内存的所有资源被回收以后,这个进程就处于死亡状态。

进程的死亡状态是无法看到的,因为只有在回收完成的那一刻才会出现,所以本喵这里也无法演示。

僵尸状态(Z)

僵尸状态就是僵死状态,它是在死亡状态之前的一个状态,当一个进程出现问题或者被杀掉以后,但是它所占的内存资源没有被回收,此时这个进程的状态就是僵尸状态。

  • 挂掉以后的进程是由该进程的父进程进行资源回收的。

【Linux学习】进程概念(上)

上面的代码中,父进程中并没有回收子进程资源的操作,也就是子进程挂掉以后,父进程什么都不会做。

【Linux学习】进程概念(上)

原本父进程和子进程都在执行,如上图中绿色框,当使用kill -9 杀掉子进程后,便只剩下父进程了,如上图蓝色框。

【Linux学习】进程概念(上)
此时子进程的状态就是僵尸状态,如上图中紫色框,显示的是Z+。此时子进程已经挂掉了,但是它的所占的内存资源没有人清理。

总结:

  1. Linux操作系统中,进程的状态是没有阻塞,挂起等状态的,普遍操作系统中的阻塞状态,在Linux系统中体现为睡觉,深度睡眠,暂停,追踪暂停等状态表现出来。
  2. 在Linux系统中的进程处于上诉状态中,是否被挂起是不得而知的,因为操作系统对于是否挂起完全是自己决定的,用户也不用知晓,也没必要知晓。

????两种特殊的进程

⚽僵尸进程

僵尸进程就是处于僵尸状态的进程,所以这里不再讲解什么是僵尸状态。僵尸状态的存在是有危害的,就像在堆区上动态开辟的空间没有释放一样,也是会造成内存泄漏的。

僵尸进程PCB中的task_struct结构体对象以及对应的二进制代码如果一直得不到释放,那么就会一直存在,就会占用内存的空间,导致正常运行的进程空间不足,造成内存泄漏。

⚽孤儿进程

顾名思义,孤儿进程就是没有父进程的子进程。

【Linux学习】进程概念(上)
原本父子进程一起在运行,杀掉父进程以后就剩下了子进程在运行。

【Linux学习】进程概念(上)
查看子进程的状态,发现子进程的PPID是1,不是被杀掉的那个父进程的PID。

【Linux学习】进程概念(上)
而PID为1的进程是操作系统,如上图中的蓝色框。

  • 一个进程成为孤儿进程后,会被操作系统领养,此时操作系统就是它的父进程。

操作系统领养孤儿进程的目的是为了能够合理的管理资源,当孤儿进程挂掉以后,它所占的内存资源能够被操作系统回收。

注意:

  1. 被杀掉的父进程不会成为僵尸进程,因为它的父进程是bash,会将其所占的资源回收。
  2. 一个进程变成孤儿进程时,也从一个前台进程变成了后台进程,需要使用kill -9 来停止。