【Linux进程篇4】谈:操作系统进程调度各种基本状态(运行,挂起,阻塞等)
---------------------------------------------------------------------------------------------------------------------------------
每日鸡汤:无须因为没见到别人努力的过程就盲目地去迷信天赋。努力,才是高手不变的功夫。
---------------------------------------------------------------------------------------------------------------------------------
目录
一:操作系统的进程状态
1.1:运行状态
1.2:阻塞状态
1.3:挂起状态
二:Linux系统进程状态的维护
2.1:Linux运行态
2.2:Linux阻塞态(浅度睡眠)
2.3:Linux阻塞态(深度睡眠)
2.4:Linux暂停态(特殊的阻塞态)
2.5:Linux死亡态和僵尸态
一:操作系统的进程状态
本质:将进程放入到不同的队列当中排队。 【运行队列,阻塞队列,挂起队列,...】
1.1:运行状态
定义:进程正在CPU上执行。
本质:进程正在CPU运行队列上排队。
特点:
- 进程已经获得了CPU资源并正在执行其指令。
- 在单处理器系统中,同一时刻只有一个进程处于运行态。
- 在多处理器系统中,可以有多个进程同时处于运行态,每个处理器上运行一个进程
进程运行队列详图:
在运行队列当中的进程,表示该队列当中的进程已经准备好被CPU调度了。可以随时运行。
即,凡是处于运行队列的所有进程,所有的进程所处的状态是运行状态,也叫做R态(运行态),即已经做好准备了,随时可以被CPU调度运行。
那么,一个进程只要把自己放在CPU上开始运行了,是不是一直到执行完毕才把自己从CPU中放下来?
答:不是。CPU给每一个进程都有一个名叫 时间片 的概念。假设时间片是10ms,那么只要一个进程在CPU上待10ms之后,就会被强制退出,CPU去执行下一个进程。这个没完成的进程继续排到运行队列后面。
这样就能得出一个概念,并发执行。
在一个时间段内,所有的进程代码都会被执行! ——并发执行
因为有了时间片, 所以肯定会有,大量的把进程从CPU上放上去再拿下来的动作。 这就是进程切块。
1.2:阻塞状态
定义:进程因等待某些事件(如I/O操作完成、信号量等)而暂时停止执行。
本质:进程正在CPU阻塞队列上排队。
特点:
- 进程不能继续执行,直到它所等待的事件发生。
- 阻塞队列中的进程不会被调度到CPU上,直到它们重新进入就绪态
我们所熟知的计算机管理软硬件都是通过”先描述,再组织“六字真言来管理的!!!
所以,就可以通过 ”先描述,再组织“ 来管理好硬件设备。
先描述外部设备:
struct dev
{int type;int status;struct task_struct* waitqueue; //等待队列struct task_struct* head;//...
}
进程阻塞队列详图:
一些进程需要访问一系列硬件资源才能正常运行。例如,从键盘中读取数据。等待键盘输入的进程状态称之为阻塞状态。链入阻塞队列的进程。
当从键盘读取到数据之后,再将该进程链入到运行队列当中。
1.3:挂起状态
再进程阻塞状态,等待资源的过程中,突然操作系统内部的内存资源严重不足了。所以为了保证正常的情况下要省出来一些内存资源。
当进程处于运行状态时,并且进程没有被CPU真正调度的过程中(在运行队列或者阻塞队列排队的过程中),该进程处于 空闲状态 。空闲状态时,空间资源不足,会将该进程的代码和数据换出到磁盘外设中,只保留该进程的PCB数据结构体排列,此省下的空间资源给别的进程使用。当该进程被CPU调度需要的时候再将该进程的代码和数据从磁盘外设中换入到内存中。这种将代码和数据换出到外设,只保留该进程的PCB排列的状态称之为 挂起状态。
进程阻塞挂起队列详图:
当需要运行该进程的时候,再将该进程的代码和数据换入到内存中。
二:Linux系统进程状态的维护
Linux状态,看看Linux内核源代码怎么说。下面的状态在kernel源代码里定义:
/*
* 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 */ // 僵尸态
};
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep)。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
进程状态查看:ps aux / ps axj 命令
2.1:Linux运行态
查看运行态【R】
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{while(1){ sleep(1);printf("Hello Linux\n");}return 0;
}
死循环打印 Hello Linux。查看进程的运行状态。
但是发现为什么该进程的状态是S阻塞态呢? 原因就是,printf函数需要通过访问外部设备,其中99%都在等待,1%运行,即大部分时间都是S阻塞状态。
那么怎么才能查看到进程状态是 R运行态呢?将不需要printf打印。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{while(1);return 0;
}
查看进程运行状态:
2.2:Linux阻塞态(浅度睡眠)
查看阻塞态S
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{int a = 0;scanf("%d",&a);printf("a = %d\n",a);return 0;
}
需要等待键盘输入,该进程才能继续运行。
所以:当等待别的某种资源(键盘)时,进程的状态就是S状态。“等待”
2.3:Linux阻塞态(深度睡眠)
操作系统会杀掉进程的,即,操作系统在工作的时候若能正常运行行则正常运行,内存空间资源不足时,就会进行空间挂起置换,还不行的话操作系统就开始杀掉进程了。
但是操作系统杀掉该进程后,数据就会”消失“,若是非常重要的数据就会造成非常大的问题。
所以这就要给该进程一个”免死金牌“(D状态),当操作系统想要杀死该进程时,看到这一免死金牌(该进程的D状态),就不会杀死它。
即,如何避免被杀掉?
在进程等待磁盘写入任务期间,这个进程要不能被任何人杀掉【处于D状态】。在D深度睡眠状态,任何人都杀不掉,包括OS,当文件信息写入磁盘完毕后,从D状态->S状态。
D状态:不响应OS的任何请求。即尽量不能让进程处于深度睡眠D状态。
当然,电脑上深度睡眠的进程还是非常少的。尽量不要使进程处于深度睡眠状态 ,处于深度睡眠状态操作系统就管不住该进程了,容易出现问题。
2.4:Linux暂停态(特殊的阻塞态)
目前我们 T / t 状态先都看作是暂停状态。
命令:kill -l
- 18信号(SIGCONT):继续该进程
- 19信号(SIGSTOP):暂停该进程
看个死循环例子:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{while(1){ sleep(1);printf("Hello Linux\n");}return 0;
}
暂停该进程,kill -19 该进程的PID
查看该进程状态:
为 T 暂停状态。
继续该进程,kill -18 该进程的PID
2.5:Linux死亡态和僵尸态
X:死亡状态【终止态】
Z:Zombie【僵尸态】
一个进程死亡时,但是操作系统并没有确定该进程是否死掉,这时该进程就会进入另一个状态——僵尸状态。
据比如生活中的例子:人死亡入棺例子
即:子进程死掉以后,关于子进程指向的资源要先给父进程。
僵尸进程:进程一般退出的时候,如果父进程没有主动回收子进程的资源信息,子进程会一直让自己处于Z状态(僵尸状态),进程的相关资源尤其是task_struct数据结构体不能被释放。
僵尸进程的危害:进程一旦成为僵尸进程,那么该进程指向的空间资源就不会被释放,即空间资源会被一直占用,最总导致内存泄漏。
看一个僵尸进程的例子:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{pid_t id = fork();if(id == 0){//子进程int cnt = 5;while(cnt){printf("我是子进程,pid: %d, ppid: %d, cnt: %d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(0);}else{// 父进程while(1){printf("我是父进程,pid: %d, ppid: %d, cnt1 = %d\n",getpid(),getppid(), cnt1);sleep(1);}}return 0;
}
运行情况:
需要等待父进程运行完毕后,才能将子进程的资源回收,但在此期间,子进程的状态都是僵尸态Z。
总结僵尸进程:
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。