Linux:进程的状态

进程的状态

当一个可执行程序被载入内存中时,获得PCB,那么它就变成一个进程,进程的状态常见的有以下三种

我们把进程的状态分为阻塞,就绪,执行三个状态,这三个状态分别对应三个整形变量,进程的状态的本质就是整形变量,操作系统通过访问PCB的整形变量来管理进程

以下是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 */
};

 这些进程状态后面都有一个数字,而这些数字都是2的倍数

为什么都是2的倍数呢?

在我们第一行有描述,这些进程的状态是用一个bitmap的位图存储起来的,也就是用二进制的方式存储起来的

R对应00000000,D对应00000010,Z对应01000000

所以在源代码中进程的状态要用整形来标识

进程状态的意义

进程状态让我们的操作系统可以将进程需要的操作分配给它,让进程得到需要的操作

比如我们都学过C语言,请看下面代码

#include<stdio.h>
int main()
{
    int num;
    scanf("%d",&num);
    printf("%d",num);
}

当我们执行这串代码时,系统会提示我们需要输入数据,这时进程停止运行,等待我们输入指令,进程的状态的意义就是让操作系统给进程分配需要的操作,进程处于不同的状态需要的操作也不同

 运行状态

CPU会指定一个链表成为它的运行队列,链表上的所有数据都是处在运行状态中的。

我们知道PCB是一个结构体,在上面的图片当中,链表节点其实也是一个结构体,它的结构类型是如上图所示,这个结构体里面存在两个节点prev和next,

CPU和操作系统可以通过这个结构体来查找队列当中的数据,当我们需要这个PCB当中的其它数据时,我们可以利用这个链表节点的地址,通过计算数据和链表地址的相对位置来计算出数据的地址,从而获取数据,也就是宏替换,操作系统中有一种专门的宏来计算数据的地址

为什么一个PCB会有这么多链表节点,是因为操作系统中存在许多种状态,不同的状态对应不同的链表,通过这种方式让所有的PCB组成许多种不同的队列类型,也就实现了许多种不同的进程状态,

当进程处于阻塞状态时就把进程链入阻塞队列中,等待获取数据,当进程需要网卡的时候,九江进程链入网卡队列当中。不同的需求对应不同的状态

R状态

我们先写一个正在运行的程序

#include<stdio.h>
#include<unistd.h>
int main()
{
	while(1)
	{
		printf("我是一个进程\n");
		sleep(1);
	}
}

我们可以用ps axj 来查看进程的状态

如果只用ps axj会显示所有进程的状态不方便我们查找,所以我们可以配合grep来使用

ps axj | head -1 && ps axj | grep text | grep -v grep

这里ps axj | head -1 是为了显示状态栏,这里grep -v grep 是因为grep也是一个进程,我们需要将它在显示时删除。

这里我们看到了text进程是处于睡眠状态的原因是当text在打印时,CPU已经将text进程执行完毕了,所以我们只能看到睡眠状态,CPU的运行速度是远远高于打印速度的

不过我们还是可以查看到text的运行状态

 我们只要让程序一直处于运行状态就可以了

#include<stdio.h>
#include<unistd.h>
int main()
{
	while(1)
	{

	}
}

再运行程序并且查找

 

我们可以发现进程的状态是R运行状态

 为什么状态时R+的状态呢

是因为我们的进程是再前台运行,进程的信息会不断地在前台打印出来,这样我们就无法在窗口输入信息,

要想让进程在后台运行,只要加上&就可以了

 

这样看似进程没有得到运行,其实我们查看一下进程信息就能发现了

 

进程仍然是处于运行状态

那么如何关闭这个进程呢

我们可以看到当我们让进程在后台运行时它会给我们一个PID

前台进程我们可以Ctrl+C来结束进程

后台进程我们可以在命令栏输入kill 进程PID来结束进程 此命令不一定能杀死进程

后台进程 kill -9 PID可以强制杀死进程,此命令不可被忽略

S状态

组赛状态

这个状态的进程在等待操作系统分配资源给此进程,一般是在我们使用scanf的时候,当我们的程序运行到scanf的时候,系统判定进程需要获取键盘的数据,程序停止运行,进程被链入阻塞队列当中等待键盘输入数据,当键盘输入数据后,进程链入运行队列当中,继续运行。

#include<stdio.h>
int main()
{
    int NUM;
    scanf("%d",&NUM);
}

让我们运行一下程序 

 

此时进程处于阻塞队列当中,等待键盘输入数据 

 重点:这个进程处于阻塞队列当中,但是它可以被强制终止S的状态就是浅度睡眠或可终止睡眠状态,我们可以在它等待资源时用Ctrl+C终止进程

D状态

D状态就是不可中断睡眠状态或深度睡眠状态,它是不可以被操作系统结束的进程,为什么会存在D状态,是因为当我们操作系统在处理大量数据的时候,会存在非常多的进程,这么多的进程会让我们的CPU的负担非常大,所以操作系统会杀死一些进程来减少CPU的负担,就比如当我们在用一个比较差的设备去游玩一个需要高性能的游戏,但是你的CPU无法承担游戏的进程,为了保护CPU,所以操作系统将游戏的进程杀死了,所以就造成了游戏的闪退效果。但是当我们系统在处理一些重要数据时,如果操作系统贸然杀死此进程那么就会导致,重要数据丢失,那么我们就需要将这个进程标记为不可被杀死状态,也就是D进程,但是演示D进程是非常危险的行为,所以这里就不能做演示了,不然电脑容易炸了。

T进程 

T进程就是处于阻塞的进程,我们可以手动将S进程变成T进程,那么该进程将会停止运行,但是该进程的代码和数据都将会保留下来等待进程再次恢复运行。

我们先运行一个死循环进程

#include<stdio.h>
#include<unistd.h>
int main()
{
	while(1)
	{
		printf("我是一个进程\n");
		sleep(1);
	}
}

运行进程

 

此时我们进程会不断打印 

 此时进程状态

 阻塞进程 

kill -19 PID

此时进程状态 

 

 此时将不再打印,进程中止,然后我们再恢复进程

kill -18 PID

 

 进程重新打印,重新运行

 但是如果我们使用Ctrl+C是无法中止程序的运行的,所以也就说明,程序是在后台运行

t状态

t状态是一个阻塞状态,但其实是被追踪的阻塞状态,那什么是被追踪的阻塞状态,就是需要得到命令才会开始执行,就比如我们在调试代码的时候,只有当我们输入下一步命令时,调试信息才会进行到下一步,我们可以来实验一下

当前目录文件

 我们将代码编译成调试状态

gcc -g text.c -o text1

执行调试

gdc text1

在我们打完断点并且运行调试之后,检查进程状态

 这时我们的进程处于一个t状态。

挂起状态

挂起状态也是一种阻塞状态

 

当进程内存不足时,操作系统会将一些处于阻塞状态的进程的数据和代码保存在磁盘中,但PCB仍然处于内存中,这种操作成为唤出,将内存空出一部分给其它进程,如果轮到该进程运行时,操作系统又会从磁盘中拿出与之对应的代码和数据 给PCB,PCB可以继续运行,就像是我们玩游戏,当我们启动游戏的时候我们的装备数据和等级数据会加载到内存中,当我们关闭游戏时,那么这部分内存我们已经不需要了,我们可以将它存储在磁盘当中,等下次需要使用再唤出。

X状态

 X状态表示死亡状态,但是这个状态存在的时间非常短,当我们进入这个状态时,CPU以一种非常快的速度将这种状态的进程释放,所以我们只要知道这种状态就可以了。

Z状态

Z状态的全拼为zombie,意思是僵尸的意思,是因为子进程已经运行结束准备释放,但父进程仍未结束,子进程需要父进程将它自己释放,子进程等待释放的过程所处的状态称为僵尸进程

我们来实验一下

#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
	pid_t id=fork();
	if(id==0)
	{
		int num=5;
		while(num--)
		{
			printf("我是子进程我的PID是%d,我的父进程PID是:%d\n",getpid(),getppid());
			sleep(1);
		}
	}
	else
        {
                while(1)
                {
                        printf("我是子进程我的PID是%d,我的父进程PID是:%d\n",getpid(),getppid());
                        sleep(1);
                }
        }


}

 过了5秒后

 

 此时就是僵尸进程了,等待父进程回收,但是再等待的过程中会造成资源的浪费

孤儿进程

当父进程先于子进程退出时,那么子进程会被INIT托管,也就是PID为1的进程

让我们看代码

#include<sys/types.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
	pid_t id=fork();
	if(id==0)
	{
		while(1)
		{
			printf("我是子进程我的PID是%d,我的父进程PID是:%d\n",getpid(),getppid());
			sleep(1);
		}
	}
	else
        {
		int num=5;
                while(num--)
                {
                        printf("我是父进程我的PID是%d,我的父进程PID是:%d\n",getpid(),getppid());
                        sleep(1);
                }
        }


}

当我们运行程序5秒后

 

父进程已经退出,子进程有PID为1的进程托管,此时子进程为孤儿进程,资源由操作系统回收 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值