目录
一 Linux操作系统进程状态
在OS中存在大量的task_struct结构体。用来维护一个一个的进程。task_struct中一般保存该进程的属性值,其中有一个属性值叫做status,是int类型的。操作系统会将各种状态定义成宏:比如#define NEW 1……
那么task_struct的status维护对应的int值,就变成了维护对应的状态。
二 大体分类
一般简单地划分成:新建状态,阻塞状态,运行状态,挂起状态四个状态。
新建状态
刚被创建,暂时不能被运行。其实一个进程创建出来就是为了被调度,这个状态是理论层面上为了自圆其说存在的一个状态,和实际操作上又有所不同。
运行状态
之前提到过,CPU会通过调度算法来执行相应的运行队列。那么task_struct结构体在运行队列中排队,就叫做运行态。这时候可以是正在被执行,也可以是在运行队列中等待被执行。
阻塞状态
系统中存在各种资源,网卡,磁盘,显卡等其他设备,多个进程访问除了CPU以外的设备的队列,叫做阻塞队列。
比如我今天写的一段程序,他需要先编译链接运行变成一个进程,被CPU调度。CPU在调度这个进程的时候,发现里面有scanf的语句,需要从键盘上读取数据。CPU就把他放到了等待磁盘资源的这个队列中去,(有可能有多个文件同时需要访问磁盘,向磁盘中写数据,因此即使是磁盘也会有队列)等完成了磁盘资源的等待之后,才放回CPU重新执行。
挂起状态
内核或者操作系统需要维护的一种状态。磁盘上存在很多个分区,其中有一个叫做SWQP分区。内存里面有很多进程(代码+数据+对应的PCB结构体)存储着,他们需要被执行。当进程数量太多了,内存资源快不足了的时候,操作系统就会将长时间不执行的进程的代码和数据换出到SWAP的磁盘中,只保留task_struct结构体。因为一个进程实际上占空间最多的是他的代码和数据。在需要运行这个进程的时候,就将对应的代码和数据从SWAP分区中放回内存的运行队列中,等待调度器来进行调度。
在上述的过程中,内存中只有对应进程的task_struct的进程状态,叫做挂起状态。
SWAP分区一般不会满,因为OS不会无脑换入换出,换入换出的过程实际上是IO的过程,会影响效率。
三 Linux内核中进程的其中状态
一般在linux内核中,进程存在七种状态。R S D T t X Z这几种。他们保存在一个指针数组中。
static const char* const task_state_array[] = {
"R(running)",//0 运行状态
"S(sleep)",//1 可中断睡眠状态
"D(disk sleep)"//2 不可中断睡眠状态
"T(stopped)",//4 停止状态
"t(tracing stop)",//8 针对于调试的停止状态
"X(dead)",//16 死亡状态
"Z(zombile)",//32 僵尸状态
};
关于前台进程和后台进程:
+ 代表这个任务属于前台进程,可以被结束。前台任务启动,执行命令没有效果。不带+的叫做后台进程或者常驻进程,他在执行的时候,不能被终止,并且命令还能执行。
关于信号:由于后续演示进程状态的时候需要用到,因此简单引入。
kill -9 管理员信号 杀死这个进程
kill -19 暂停这个进程
kill -18 继续这个进程
前台进程和后台进程(&)不影响命令行操作
1 R 运行态——调度CPU资源(运行状态)
根据我们自己的理解,执行一个有循环语句的程序的话,查看这个进程的状态,他应该是处于R状态。但是实际上,当我们带上printf的时候,是S状态;不带的时候才是R状态。
为什么会这样呢?
CPU运行速度非常快,而printf调用键盘资源的运行速度与CPU的运行速度相比就慢了很多。在程序执行的时候,把阻塞队列中等待资源的数据放到被CPU调度的运行速度中(printf的过程),由于阻塞队列所占的时间很长,运行队列所占的时间非常短,我们观察进程状态的时候,也很难观察到对应的R状态。
2 S D 睡眠状态——等待资源就绪(阻塞状态)
①S 可中断睡眠(也可能是挂起状态)
上述演示的过程中也出现了对应的S状态。这种睡眠状态是因为在等待资源就绪。S是可以被打断的意思——也就是说,你可以杀死这个等待资源就绪的进程。
②D 磁盘睡眠 也叫不可中断睡眠
为什么会有这样的一种睡眠状态呢?
有这样的一个场景:
执行某文件的读写,进程A要访问磁盘中的某个位置,将数据刷新到磁盘中。(数据量非常大)
磁盘去执行读写的任务了。这之后成功或者失败读写数据的结果,都会反馈给CPU,此时该进程是阻塞状态。
当服务器压力过大的时候,OS会通过一定的手段,杀掉一些进程,起到节省空间的作用。OS如果此时内存状态非常紧张,服务器压力过大,看到进程A在休眠,干掉了进程A(换言之,也就是该睡眠可以被中断) 。
那么此时数据如果读写失败了,怎么报告?向谁报告?CPU中的进程都被杀掉了。磁盘就会把对应的数据扔掉。造成数据丢失。
导致磁盘读写数据丢失最本质的问题:进程A被杀掉了。
因此会有一个相关的睡眠状态,叫做D状态。不可被中断,叫做深度睡眠,也就是不可被杀掉的睡眠。
只能当最后取得了对应的读写结果,才会醒来。除此之外,只有关机重启才能切换。
即使是管理员信号 kill -9也不可以杀死该进程。
3 T t 暂停状态——停住代码
暂停和休眠的区别:休眠在系统层面上是阻塞状态,等待某种资源。
T本身没有等待任何资源,只是把对应的代码停住了。
①T
上述过程中已经演示了T状态。本身该程序本来在运行,结果发送了19号信号,让该进程暂停了,就出现了对应的T状态。
②t
也是暂停状态,只不过是在调试的过程中出现的一种状态。
用gdb调试的时候,打上断点再来观察进程的状态就能观察到了
4 X Z 终止状态
①X状态 (dead),终止状态,他是因为进程已经挂掉了,并且可以被操作系统回收了但是有时候,OS很忙,可能没来得及及时回收这个进程,于是就会出现这样的一个状态。但是她的瞬时性很强,很难模拟。
②Z状态(僵尸状态)
是什么?父进程还在运行,但是子进程已经退掉,并且没有把对应的信息反馈给父进程,那么此时子进程就不能被OS回收,这个状态叫做僵尸状态。
为什么会存在这样的状态?父进程或者OS关心这个子进程的状态。因为父进程创建子进程,是需要子进程帮忙实现自己的一部分任务,那么要知道任务完成的怎么样了。因此父进程主动检测子进程状态。维持该状态,为了让父进程和OS进行回收。
一般是由父进程回收的,如果是出现了僵尸进程这种状态之后,父进程也挂了,就要由OS回收。
模拟:
危害:
一个进程退出:PCB保留,将代码和数据释放掉。
系统中如果恶意程序:创建子进程,处于僵尸状态,不释放,父进程不回收。那么会一直占用资源(CPU)导致内存泄漏。维护退出状态本身是需要数据维护的。
拓展:孤儿进程
创建子进程,子进程还没退出,父进程先挂掉了。这叫孤儿进程
一般回收子进程,都是由父进程回收的。创建子进程,子进程还没退出,父进程先退出了。那么此时谁回收子进程的资源呢?此时该子进程必须被领养,是由1号进程(Init,OS)领养。
但是如果操作系统子进程过多的话,也是需要一步步回收的。父进程为1号进程的Z状态进程很难观察到。因为速度很快。