【Linux】进程初阶(2)——进程状态
目录
前言
2.1进程状态理解
2.2Linux下的进程状态
2.2.1简介
2.2.2状态详解
2.3僵尸进程与孤儿进程
2.3.1僵尸进程
2.3.2孤儿进程
前言
根据前面对进程的基本了解,浅浅了解了进程。下面更进一步探讨进程,来了解Linux下的进程状态。更多Linux学习内容参考:Linux专栏
2.1进程状态理解
在宏观的计算机系统中,一般进程状态及状态间关系如下图:

而其中最核心的是运行、阻塞和挂起状态,下面让我们一起来认识这三个状态。
在操作系统为进程分配硬件资源时,每个硬件程序会有一个调度队列,队列中会储存每个进程的PCB,而一般我们将处于CPU调度队列当中的进程称为运行状态。
下面以Linux的task_strcut为例:操作系统为管理硬件,也会对硬件进行描述,类似有一个储存硬件信息的结构体,在该结构体中又会有相应进程的调度队列

像在CPU调度队列的进程状态就是运行状态。
对于阻塞状态,我们先来看看程序运行时怎样就是阻塞:
#include<stdio.h>
#include<unistd.h>int main()
{int a;printf("等待键盘输入:");scanf("%d", &a);printf("%d\n", a);return 0;
}
运行结果如下:

这时等待键盘输入的状态就可以称为进程阻塞了,此时的进程需要调用键盘输入,即等待键盘就绪,更进一层面也就是等待硬件就绪。那么程序怎么知道硬件就没就绪呢?操作系统作为软件与硬件的管理者,也会对硬件进行结构体描述,在每个硬件的结构体中也会有一个等待队列。因此将进程结构体处于除CPU之外队列的状态,称为阻塞状态。

那什么又叫挂起呢?挂起是主要应对进程在运行过程中内存不足的情况。在操作系统硬件的磁盘中,会有一个swap分区,用来做内存不足的情况下,来储存还未来得及运行的进程代码和数据。此时进程处于swap分区的状态叫做挂起状态。

除此之外,像例子中这种在运行过程时挂起的状态叫做运行挂起;而若进程在等待硬件就绪处于阻塞状态时被挂起,则称为阻塞挂起。
当内存足够时,储存在swap分区中的进程就是通过swap out换出到内存,而若仍然内存不足,操作系统则会选择直接删除部分进程。
2.2Linux下的进程状态
2.2.1简介
上面进程状态主要是对于全部操作系统的设计哲学,而对于特定的操作系统会有所差别。下面以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 */
}
为了更方便了解操作系统中的进程的状态,我们可使用ps aux | ps axj 命令来查看进程状态。
• a:显⽰⼀个终端所有的进程,包括其他⽤⼾的进程。
• x:显⽰没有控制终端的进程,例如后台运⾏的守护进程。
• j:显⽰进程归属的进程组ID、会话ID、⽗进程ID,以及与作业控制相关的信息
• u:以⽤⼾为中⼼的格式显⽰进程信息,提供进程的详细信息,如⽤⼾、CPU和内存使⽤情况等
2.2.2状态详解
R状态
R(runing)状态表示进程正在运行
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{pid_t id = getpid();while(1){printf("这是一个进程,pid为:%d\n", id);sleep(1);}return 0;
}
我们通过运行自己创建的进程来看看是不是处于R状态:

可以看到这时我们创建的进程pid为8243,而8243的状态为S+,那我们程序正在运行,应该是R状态为什么会是S+状态呢?
S状态
S(sleeping)在Linux中称为浅度睡眠状态,与操作系统原理中的阻塞状态类似,进程中需要调用某个硬件,并且正在等待该硬件就绪。
而在状态后面带个"+"则代表该进程属于前台进程。
我们的进程处于S状态的主要原因是在程序中调用的printf,而printf本质是向显示器上打印数据,且大部分时间是在等待显示器这个硬件资源,因此就处于S。
只需要我们将printf取消调用就是能看见R状态

可以看到此时我们的process进程就处于R状态了。
D状态
D(disk sleeping)状态在Linxu中称为深度睡眠状态,是Linux中特有的一种状态。
要谈深度睡眠就要与浅度睡眠进行对比。浅度睡眠是为了表示正在等待硬件就绪的状态,而深度睡眠则表示一种等待内存给完资源的状态。例如一个进程在运行时需要内存开辟500MB大小的空间给进程,而内存开辟数据空间需要时间,此时进程就在等待内存开辟完成,若此时进程像别的硬件等待一样处于S状态而被优化关闭,那么就会造成内存会没来得及开辟完成,进程就提前结束的情况,进而造成数据丢失。因此Linux中设立了D状态,以防止处于该状态的进程被提前结束。
T状态与t状态
T(stopped)状态在Linux中称为暂停状态。而t状态则称为追踪暂停状态,一般只在程序被调试的过程中产生。
暂停状态与休眠状态不同的是T状态一般为进程被外部信号主动暂停且只能通过信号唤醒,而S状态则是进程一般在等待某个事件或资源到来时主动进入。
X状态
X(dead状态)也被称为消亡状态,为瞬时状态,一般无法被观测到。
谈到X状态,那么已经消亡的进程就会马上消失吗?例如现实世界生物的死亡,我们会先确定其死因。同样在操作系统中一个进程的消亡也需要检查其消亡后的进程信息,此时操作系统不会马上将消亡进程的资源进行释放,而是对其进行临时保留。此时进程处于已结束而资源未被回收的状态称为Z状态(僵尸状态)。
Z状态
Z(zombie)状态也叫僵尸状态,是进程已消亡而其资源仍被操作系统保留的一种状态。
2.3僵尸进程与孤儿进程
下面进一步探讨父子进程间消亡的先后。
2.3.1僵尸进程
当进程消亡后会先处于Z状态,而若未将该资源回收,会一直处于僵尸状态,此时的进程被称为僵尸进程。
在操作系统中运行进程一般都是bash为父进程,因此会自动回收。而若我们自己创建的父子进程中将子进程先消亡,而父进程保持运行,而父进程中不对子进程消亡后资源进行回收,那么子进程就会成为僵尸进程:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{pid_t id = fork();if(id == 0){int i = 5;for(i = 0; i < 5; i++){printf("这是子进程,pid为%d\n", getpid());sleep(1);}}else{while(1){printf("这是父进程,pid为%d\n", getpid());sleep(1);}}return 0;
}
下面我们来查看下运行进程的状态

可以看到当子进程运行结果,而父进程仍在运行而未对子进程资源进行回收时,子进程就会一直处于僵尸状态,也就是说此时的子进程就会成为僵尸进程。而僵尸进程一直占用内存资源,导致其他进程所能有的资源越来越少,若僵尸进程越来越多,最后就会导致操作系统的卡顿甚至崩溃。
2.3.2孤儿进程
子比父先亡,那么子进程就有可能成为僵尸进程;那如果父进程比子进程先消亡呢?
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{pid_t id = fork();if(id != 0){int i = 5;for(i = 0; i < 5; i++){printf("这是父进程,pid为%d\n", getpid());sleep(1);}}else{while(1){printf("这是子进程,pid为%d\n", getpid());sleep(1);}}return 0;
}

可以看到此时pid为8464的子进程在父进程优先消亡后,父进程变为了pid为1的进程,pid为1的进程是什么呢?Linux系统下我们通过top命令来查看所有运行的进程:

可以看到此时pid为1的是一个叫systemd的进程,也就是系统进程。
那么为什么父进程消亡后要将其子进程的父进程变为系统进程呢?子进程消亡后的资源需要其父进程进行回收,若父进程比子进程优先消亡而不改变其父进程,那么就是导致父进程消亡的同时间接造成了僵尸进程问题。因此对于父进程优先消亡子进程,操作系统会统一将其父进程变为pid为1的系统进程,方便其后的资源回收,防止内存泄露。
