目录
[t 状态(追踪状态)](#t 状态(追踪状态))
我们知道cpu是通过一个调度队列来处理调度一个个进程的,一个cpu,一个调度队列!
一个个进程肯定有多种不同的状态,到底是执行完了还是没有,或者是一直执行某个语句不动的状态,所以我们现在详细讲讲。
进程的各种状态
运行状态
运行状态比较好理解,我们说只要进程在调度队列中就都是运行状态(running)。

为什么同一个节点可以在不同的数据结构中呢?怎么实现的?
我们之前在学习数据结构的时候,同一个节点只能在一个数据结构中,而OS中却不是这样的,它可以在不同的数据结构中。
有了list_head,我们只需要管理list_head就能间接管理进程pcb。
我们可以在task_struct 中设计多个list_head结构体,这样多个数据结构容器只需要对各自的list_head结构体管理就能间接管理task_struct:
阻塞状态
阻塞状态:等待某种设备(键盘,显示器,网卡,磁盘,摄像头......)或者资源的就绪。
我们最常接触到的阻塞就是程序运行在scanf语句的时候,等待键盘的输入,说白了就是在等待键盘就绪,这时候进程从运行状态变为阻塞状态。
更深理解:
我们知道OS管理硬件也是和进程一样一个个结构体描述硬件,在每一个硬件中其实也有一个队列,叫等待队列。程序刚开始在运行的时候是运行状态,在执行到sacnf语句时,这个过程,进程pcb从调度队列链出,链入键盘的等待队列。此时进程就是阻塞状态。
当我们输入完毕,键盘就绪,OS判断等待队列是否为空,不为空将这个进程pcb就会重新链出等待队列,链入调度队列,变为运行状态。
设备管理:


挂起状态
磁盘中有一个swap交换分区(大小是内存的1.5倍或2倍等),当内存资源严重不足的情况下,OS将阻塞进程的代码和数据唤入swap交换分区,此时这些进程状态就叫阻塞挂起状态。当这些进程要被调度时,将这些进程的代码和数据再唤入内存中。有时甚至会将调度队列中的末尾进程唤入swap交换分区。
图:

linux中的进程状态、
进程状态查看
ps aux / ps axj 命令
- a:显⽰⼀个终端所有的进程,包括其他⽤⼾的进程。
- x:显⽰没有控制终端的进程,例如后台运⾏的守护进程。
- j:显⽰进程归属的进程组ID、会话ID、⽗进程ID,以及与作业控制相关的信息
- u:以⽤⼾为中⼼的格式显⽰进程信息,提供进程的详细信息,如⽤⼾、CPU和内存使⽤情况等

在linux内核中,linux状态和以上的状态有所不同,上面只是适合所有操作系统,但是不同操作系统之间还是有差别的。

在每个task_struct中都有一个变量记录一下进程的状态,上图是一个状态数组,而一般task_struct中的这个变量其实就是一个整数(每个不同的整数代表不同的状态)。
- R ----运行状态
- S ----浅睡眠状态(可中断睡眠状态)
- D ----深睡眠状态(不可中断睡眠状态)
- S和D其实都属阻塞状态
- t ----追踪状态
- T ----暂停状态
- X ----死亡状态
- Z----僵尸状态
我们看看具体概念:
- R运⾏状态(running):并不意味着进程⼀定在运⾏中,它表明进程要么是在运⾏中要么在运⾏ 队列⾥。
- S睡眠状态(sleeping):意味着进程在等待事件完成(这⾥的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
- D磁盘休眠状态(Disksleep)有时候也叫不可中断睡眠状态(uninterruptiblesleep),在这个 状态的进程通常会等待IO的结束。
- T停⽌状态(stopped):可以通过发送SIGSTOP信号给进程来停⽌(T)进程。这个被暂停的 进程可以通过发送SIGCONT信号让进程继续运⾏。
- X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表⾥看到这个状态。
我们一个个来讲解!!!
S状态(浅睡眠)

我们可以换成后台输入运行,此时我们的命令行可以继续输入其他命令:

t 状态(追踪状态)
当一个程序被debug的时候,就是一个追踪状态。

T状态(暂停状态)

kill命令手册
kill有很多命令,我们可以查:

其中:
- -9 :杀掉进程
- -18:恢复进程
- -19:暂停进程
D状态(深度睡眠)
首先为什么要有D状态呢?
看一个情景:
某个进程,要将100MB数据写入磁盘,此时进程状态是S状态(等待数据全部写入磁盘,写完之后会告诉进程成功与否(返回值实现)),如果此时内存空间严重不足,我们知道OS会将一些阻塞进程甚至调度队列末尾进程挂起,可是这样仍然不足呢?那么OS很有可能将这个写入磁盘进程杀掉,这个进程杀掉了,那这100MB数据怎么办?如果写入磁盘时,磁盘空间也不足,写入失败了,本来要返回告诉这个进程失败信息,但是此时进程被杀掉了,也就是说现在用户也不知道这100MB数据写入失败了,这100MB数据就丢失了!如果这100MB数据是某个银行转账一天的流水呢!
所有说才会有D状态进程,不可被OS杀掉,这样就算写入失败了,用户也知道失败了!
一般在高IO流的时候才会出现!
Z状态(僵尸状态)
只要是进程,那么它一定有父进程,而当子进程运行结束,子进程的相关信息是需要被父进程获取的,而我们知道进程的相关信息是在它的pcb的,也就是说子进程运行完,此时OS可以将它的代码和数据释放掉,但是pcb不能释放掉,父进程获取完子进程信息之后,子进程正式退出!
子进程运行完之后,父进程获取子进程相关信息,子进程正式退出之前,这就是僵尸状态!
看代码:

看现象:

如果父进程一直不管,一直不回收子进程的pcb,那么子进程一直都是僵尸状态,子进程的pcb一直就得不到释放,这会导致内存泄露。
那进程内存泄露了,进程退出了,内存泄露还存不存在?
不存在。
就像之前我们学习c语言的时候,一个main函数里面,死循环开辟内存,而不释放,就会内存泄漏,而当程序结束完(return 0后),OS就自行回收内存!
那什么样的进程害怕内存泄漏呢?
我们刚刚说的那个显然是不害怕内存泄漏的,一些常驻内存的进程就会害怕内存泄漏。
常驻内存的进程就是那些启动之后不退出的,一旦启动不退出的进程。
比如操作系统就是一个启动不退出的进程,如果操作系统内核代码出现了内存泄漏,就会越来越卡。
task_struct的节点是怎么申请和释放的?
在平常的使用当中,大部分都是多进程并发的,那肯定离不开一个个的pcb去不断申请和释放,但却不是我们常认识的那种申请和释放。
有一个unuse区域,专门存储那些要释放的task_struct节点,当我们要释放某个task_struct的时候,将这个节点放入unuse区域即可,当我们要申请一个新的节点的时候,我们只需要在unuse区域中拿即可!
孤儿状态
父子进程关系中,如果父进程先退出,子进程要被1号进程领养,这个子进程就叫作孤儿进程。
这1号进程其实就是操作系统!既然被1号进程领养自然要被1号进程回收!
看代码:

现象:

子进程被领养之后就变成后台进程了!
看看1号进程:

我们可以看到,这个1号进程是叫systemd(老版本叫init)进程,它其实也有一个0号进程,但我们一开机这个0号进程就被1号进程取代了,这个我们不详细谈。
为什么1号进程需要领养呢?
前面知道,子进程需要被父进程获取信息(回收),这个时候子进程是僵尸状态,但是此时子进程没有父进程了,那么这个子进程就会内存泄漏,所以需要被领养,最后统一回收!
好了,我们下期见!