朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux进程状态的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!
C 语 言 专 栏:C语言:从入门到精通
数据结构专栏:数据结构
个 人 主 页 :stackY、
C + + 专 栏 :C++
Linux 专 栏 :Linux
目录
前言
上篇博客了解到了创建进程的两种方式,以及fork的一些简单原理,那么本期来看一下创建的进程PCB中关于描述进程状态的字段。
1. OS层面的进程状态
1.1 进程状态
在我们系统中会存在许多的进程,那么这些进程会存在多种状态,这些状态分别都是什么呢?
根据不同的操作系统,这些进程的对应状态可能有所不同,我们主要来了解常用的状态。
简单来了解一下进程状态:
进程状态就是PCB中的一个字段,简单的说就是PCB中的一个整形变量 ---- int status
所以我们就可以定一些宏来表示一些状态,将status赋值为某一状态值就表示该进程现在处于什么状态,然后就可以通过if语句判断其状态然后执行不同的任务。
所谓的状态变化,本质就是修改整型变量的值。
1.2 运行状态
我们的CPU在系统层面都会维护一个运行队列,那么什么叫做运行状态呢?
只要在运行队列中的进程。状态都是运行状态。
简单的说,处于运行队列的进程,就表示已经准备好随时被调度运行了!
1.3 阻塞状态
当CPU调度运行我们的PCB时,就会执行我们所写的一行行代码,那么我们写的代码当中肯定或多或少会存在一些访问系统资源的代码,比如一些磁盘、键盘、网卡等硬件外设,例如我们C语言中的scanf函数,和C++中的cin>>,我们是用这些语言的本质就是要从键盘读取数据,但是如果我们不输入任何东西,代码就会一直卡在这条语句这里,就代表了键盘上的数据是没有准备就绪的,表示我们进程所要访问的资源没有就绪,得到的结果就是不具备访问条件,进程代码无法继续向后执行。
上面说到了我们不输入时就表示我们要访问的资源没有就绪,那么操作系统要不要知道各种设备的状态呢?答案是肯定要知道的,所以操作系统也要对硬件设备进行管理,根据管理的本质:先描述,再组织。
所以OS中会有一个结构体对象,用来专门描述硬件设备,结构体中包含硬件设备的类型、状态、指向下一个结构体的指针、等等各种更多的属性,然后通过指针将各种结构体连接在一起形成了一个链表,这样OS对于硬件设备的管理转化成了对链表的增删查改!
上面了解了OS对于硬件外设进行管理,那么接下来OS要知道设备的这些数据有没有就绪,所以再在描述设备的结构体中添加一个字段用来表示设备的数据有没有就绪,如果这个设备的数据状态没有就绪,那么在结构体中还需要维护一个进程PCB的等待队列,数据没有就绪,直接将该进程所对应的PCB从CPU中的运行队列链接到非CPU的外设中的等待队列里面,那么这个过程就叫做该进程阻塞了!!
在操作系统中,会存在非常多的队列,等待CPU的运行队列、等待硬件设备的等待队列等等。
当OS识别到外设的数据就绪时,然后再将处于外设的等待队列中的PCB重新连接到CPU中的运行队列中!这个过程就叫做将该进程进行唤醒!
前面说到进程状态变化的本质就是①修改PCB中描述PCB状态的整形变量的值,那么现在还要加上一条:②将进程PCB链入到不同的队列中。
在这些状态变化中,OS是最先知道它所管理的设备的状态变化的,当可执行程序运行起来之后就变成了一个进程,那么OS系统在这个过程中会先检查PCB的状态、需要访问外设的数据状态,根据这些状态修改PCB中描述进程状态的整形变量,并将PCB链入到该状态所对应的队列中。
在上面我们所理解的所有的过程,都只和进程PCB有关,与进程的代码和数据没有关系!!
当一个进程阻塞了,我们用户会应该看到什么现象?为什么?
① 进程卡住了。
② pcb没有在运行队列,并且pcb状态不是running,CPU不调度你的进程了。
1.4 挂起状态(阻塞挂起)
1. 如果一个进程当前被阻塞了(处于某个外设的等待队列中),那么就注定了这个进程在它所等待的资源没有就绪时,该进程是无法被调度的。
2. 那么恰好此时,操作系统的内存资源已经严重不足了(前提),该怎么办呢?
因为OS是不允许有任何低效和浪费的情况产生的,那么既然该进程已经被阻塞了,所以它的代码和数据不能被调度执行,并且此时OS的内存已经严重不足,那么该进程的代码和数据就白白占用了一片资源,OS为了解决这种问题,就会将该进程的代码和数据交换到磁盘中,以此来节省出一块空间资源,那么这个行为就叫做该进程被挂起!
1. 操作系统你凭什么将我一个进程挂起,是不是针对我?
将内存数据置换到外设,是针对所有的阻塞进程。
2. 外设的访问速度一般是很慢的,那么将数据置换到磁盘,就表示要访问外设,那么需不需要担心访问速度的问题?
不需要担心的,此时只需要关心如何让操作系统继续运行下去,速不速度的不重要了。
3. 将数据置换到磁盘,那么是随便置换到磁盘的任何位置吗?
磁盘中会存在一个swap分区,从OS中置换出来的数据汇报存在这个分区中。
4. 外设数据状态准备就绪,如何执行呢?
当进程被OS调度,曾经置换出去的的代码和数据,又要被重新加载进来,继续执行!
这些工作全部都是由OS自动执行的!
2. Linux中的进程状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)。下面的状态在kernel源代码里定义:/* * 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 */ };
2.1 R状态(运行状态)
运行状态:并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。在之前我们使用查看进程的指令ps ajx时,将它的第一行提取出来,其中里面有一个属性:STAT表示的是进程的状态。可以通过代码来看一下该状态:使用一些循环脚本可以一直监控进程:
while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v "grep"; sleep 1;echo "#########################"; done
可以看到我们检测到的状态和我们预期的状态不一致,我们的进程明明正在运行,那么怎么是S状态?
这个S状态类似于是OS中的特殊的阻塞状态。因为我们代码中的printf函数就是要往显示器上打印“hello Linux!”,所以也就在访问外设,因为CPU的速度是非常快的,可能在访问显示器的时候显示器可能没有准备好,所以检测出来就是S状态,但是当显示器外设刚刚准备好又刚刚调度了那才能显示R状态,所以才能在众多的S状态里面看到极少的R状态:
这也表示出了访问外设已经IO操作太慢了,那么将IO操作代码删除掉就可以完整的看到进程的R状态:
2.1.1 前台进程
当我们./可执行程序,将程序启动起来时,我们再在命令行中输入指令是没有任何作用的,并且我们可以通过ctrl + c将进程终止掉的这种程序叫做前台进程。并且我们查到的进程状态中后面都会带一个+。
系统的前台只能启动一个前台进程!
当我们在启动程序的时候,在后面加上 & 就可将该进程转化到后台,变成后台进程,并且此时再输入ls、pwd这些指令都是可以执行的,使用ctrl + c并不能终止进程。此时再查到进程状态时后面就没有+:
2.2 S状态(休眠状态--浅度)
睡眠状态: 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 )。
就比如我们使用scanf输入时,我就是不输入,那么程序就会卡在这里,这种就是休眠状态,也就是类似于OS中的阻塞状态,一样的道理。
这种休眠状态叫做浅度睡眠,是可以被终止的,浅度睡眠可以对外部信号做出相应。
就比如我们可以使用kill -9 来杀掉该进程,或者可以使用ctrl + c来终止进程。
2.3 D状态(休眠状态--深度)
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
Linux操作系统在实在没办法的时候,会通过杀掉进程,来节省资源的!
深度睡眠是专门针对磁盘来设计的。
为了防止进程在向磁盘写入重要数据时被杀掉,可能会导致数据写入失败从而丢失数据的问题,将该进程设置为D状态,就表示该进程不可被杀掉,连操作系统也没有资格。
如果被用户查到了D状态,就证明计算机几乎就要挂掉了!!
2.4 T状态(停止状态)
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
在我们的Linux系统中存在许多的信号,我们可以通过kill -l 指令来查看,这些状态前面的数字就是该信号对应的编号,那么在Linux系统中这些信号就是#define定义的常量。
通过对指定进程发送19号信号,就可以停止进程,对应的进程状态也就变成了T状态,对停止的进程发送18号信号就可以使其继续执行。
可以看到暂停之后就变成了后台进程了。
在进程访问软件资源的时候,可能暂时不让进程进行访问,所以就将进程设置为T状态。
t (tracing stop)状态:在调试程序的时候,追踪程序,遇到断点,进程暂停了!
T状态和t状态本质上也是阻塞状态!
2.5 X状态(终止状态)
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
进程 = PCB + 代码和数据,这些都是在内存中占据空间的,所以进程退出的核心工作之一就是将PCB和自己的代码和数据释放掉!
2.6 Z状态(僵尸状态)
我们创建一个新的进程的本质就是为了让它帮我们完成一些事情,那么我们怎么知道这个进程把我交换给它的任务完成的如何呢?所以进程在退出的时候,会有一些退出信息,用来表示自己把任务完成的如何,那么这些信息会由操作系统写入到当前退出进程的PCB中,然后允许它的代码和数据进行释放,但是并不能直接运行进程的PCB被立即释放,得先让OS或者父进程读取到退出进程的退出信息,从而得知该进程退出的原因!
那么如果这个进程退出了,但是并没有被OS或者父进程读取,所以OS还必须维护这个退出进程的PCB结构!此时,这个进程所处的状态就叫做僵尸状态。
只有当OS或者父进程读取之后,这个进程PCB状态才会被改为X状态,然后才可以进行释放操作!
朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!