进程的状态
当一个可执行程序被载入内存中时,获得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的进程托管,此时子进程为孤儿进程,资源由操作系统回收