进程的状态也是进程属性的一种被记录在PCB中。
操作系统进程状态名词提炼:

这是对所有操作系统进程状态的总结,是一个学习具体操作系统进程状态的模板,所以说具体操作系统的进程状态概括可能与其有些不同。
一.状态解释
运行状态
我们知道进程被OS以PCB的形式组织在数据结构中管理着,而一个PCB可能同时存在于多个数据结构,其中可能就在一个叫做runqueue (调度队列)的结构中。

每个CPU对应着一个调度队列,处于调度队列中的进程就等待着被调度运行。这么一说我们可能会认为正在被调度的进程就是处于运行状态的进程,其实不是的。只要处于调度队列中的进程就是运行状态。
阻塞状态
简单来说,阻塞状态就是进程在执行的过程中需要等待软硬件的回应,在回应前进程就处于阻塞状态。我们之前也遇到过阻塞状态,比如C语言代码运行到scanf、c++代码运行到cin,这时程序都在等待我们键盘的回应。
无论软硬件,在OS中都被"先描述,在组织 "的形式管理着。

我们拿scanf的例子来展示阻塞:

当该代码运行到scanf时,它需要从键盘得取信息,其对应的进程会被调度队列删除,即不可能再被调度执行。此时键盘对应的结构体中会有一个指针(暂称wait queue),它会指向需要键盘回应的对象(可以是一连串数据),同时该对象也有指针会指向它。此时该对象对应进程就处于阻塞状态。一旦该进程从键盘处得到回应,那么它就又被连接进调度队列中等待调度执行(运行状态)。
也就是说:阻塞状态就是进程等待设备或资源就绪的状态。
挂起状态
挂起状态通常出现再比较极端的情况。

当计算机内存告急的时候,OS为了不让计算机挂掉会想进办法将内存空出来。此时那些没那么着急执行的进程对应的代码和数据会被OS唤出内存,暂存再磁盘中;但内存够了或要调度它时再被OS唤入内存。 比如处于阻塞状态的进程,当它对应的代码和数据被唤出内存后,该进程就会处于阻塞挂起状态 。当内存相当告急,即将阻塞进程对应的代码和数据暂存进磁盘后内存还是不够的话,OS将处于运行状态但还在排队等待被调度的进程对饮的代码和数据暂存入磁盘,此时这些进程处于运行挂起状态。
总结
通过比较上面三个状态的不同我们可以发现:
进程状态的变化表现之一就是进程在不同队列(也可以是其他数据结构)中流动,本质上是数据结构的增删查改。
二.理解内核链表
前面我们提到,OS中的结构体对象可以同时存在与不同的数据结构,这跟我们之前学的并不一样。先回顾普通链表:

节点中又prev和next指针指向前一个节点和后一个节点。这样的设计只能让节点存在于一个数据结构中。
在来看看OS中个数据节点如何连接的:

它是单独定义一个含prev和next 的结构体struct list_head (同时也是prev和next的类型),然后再在需要管理的节点struct tast_struct 中定义一个struct list_head 类型的成员变量link ,最后让link 中的prev 指向前一个节点中的link 变量,next 指向后一个节点中的link 成员变量。但这有一个很大的问题,如何通过结构体中某一成员变量的地址来访问整个结构体的数据?
在一个结构体变量中,成员变量所占地址是依次增大的,所以只要知道成员变量相对于结构体变量开头的位置,就可以求出结构体变量开头的地址。因此我们可以:

以0为原点,将地址0强转为"struct tast_struct*"类型,然后通过它访问到的link地址减去结构体开头的地址(0)就得到了相对距离,在这里就是访问到的link地址的大小!
对于我们要访问的tast_struct A,我们知道它其中的link地址,直接用它减去上面所求地址就可以得到&A,就可以访问整个结构体变量。
只要结构体中含多个struct list_head 类型的变量,他就可以用同样的方法同时链接进不同的带节点的数据结构。
三.Linux系统进程状态介绍及查看
Linux系统进程的具体状态有以下这些:

R(running)
R就是运行状态,以改代码为例查看状态:


可以发现该代码对应的进程状态是R+ ,为什么有个+呢?
+表示该代码在前台运行,我们可以看到它的执行情况,如果它在后台运行就看不到,后台运行:可执行文件 &

S(sleeping)
在linux里,S就是阻塞状态。

在等待我们输入时,我们可以手动删除该进程:

所以该状态也叫做浅睡眠状态 和可中断睡眠状态。
D(disk sleep)
D状态也是阻塞状态的一种,它叫做深度睡眠状态,不可打断睡眠状态 。
假如有一个进程是要向磁盘内写入信息,如果你因任何操作删除了它,那向磁盘中写入信息这个任务就会失败,同时被进程携带的这部分信息就会彻底消失!
所以Linux中专门设置D状态来应对这类情况,如果进程处于D状态,那么我们只能等它自己醒来或重启系统。
T(stopped)
T为暂停状态,如果OS判断进程出现了错误,那么它就会暂停和告诉用户,由用户判断进程是否可以继续 。我们也可以主动暂停(ctrl z )进程:

这里可能会有疑问,为什么代码运行的状态是S,这是因为printf本质上也要等待显示器回应,所以该代码运行时既可能状态为R(代码执行到printf前后),也可能时S(代码执行到printf,要等待显示器回应)。
t(tracing stop)
t也是一种暂停状态,主要出现在我们调试代码时,比如让代码在断点处停止:

X(dead)
X是死亡状态,即进程终止,因此也查不到PID(已经没了)
Z(zombie)
我们为什么要创建子进程?为了去完成某个任务。
子进程是否要将完成任务的信息(无论失败与否)告诉父进程?当然要,毕竟子进程的任务就是父进程的目标之一,父进程需要知道自己的目标完成与否。
也就是说,子进程执行结束时,必须要将自己的任务完成进程完成情况告知父进程,距离父进程接收到该消息是有一段时间的,在这一段时间内子进程将被一直维护着,就等着父进程接收它的信息,此时称该进程为僵死进程 。

那一旦父进程一直没有接收子进程的结束信息会怎么样?
会内存泄漏!。如何解决后面学习。
孤儿进程
当父进程比子进程更先退出时,子进程就会变成孤儿进程 在后台运行,如果什么都不做,那么子进程的进程完成后就会一直处于Z状态无法退出,会导致内存泄漏 ,为了解决这个问题,OS会用1进程"领养"孤儿进程,即让1进程成为它新的父进程:

1进程如下:

我们可以认为1进程就是操作系统。