一.操作系统的进程状态(广泛)
📚操作系统中存在各种进程状态的本质是为了满足各种运行场景。
1.运行状态
管理的方法是先描述再组织,操作系统对于进程的管理实际上是对该进程的进程控制块做管理,而CPU数量总是小于进程数量的,所以CPU为了管理好这些进程控制块同样采用了先描述再组织的方法,即产生一个运行队列来管理加载到CPU中的进程。当某个进程的进程控制块被放入到了CPU中的运行对列就可以说该进程处于运行状态。
CPU数量总是小于进程数量,所以一个CPU往往需要执行多个进程,也就是说一个CPU的运行队列中往往会放多个进程。
但一个CPU在一个时间内只能执行一个进程,并不是只有这个正在被CPU执行的进程才处于运行状态,那些在CPU运行队列中等待CPU来执行的进程也是处于运行状态的。
2.阻塞状态
外设(磁盘,网卡等)的速度远远慢于CPU,但是其实我们大部分的程序都要获得外设资源(比如执行printf/scanf等指令,或者从磁盘中读文件,用网卡向网络中获取资源等操作)。
外设资源也是少量的(甚至某些只有一个),也就说进程数量也是大于外设的。当CPU中正在执行的进程需要访问外设资源的时候,可能这个外设正在被访问,那么这个进程就需要等待这个外设被访问完来向它提供它所需要的资源。
如果此时CPU选择等待该进程获取到外设资源,那么CPU的效率就会被拉到和外设平齐,冯诺依曼体系结构就没有了存在的意义。所以CPU肯定是不会等待进程访问到外设资源后再继续执行该进程的。
既然外设也是少于进程的,而且外设又很慢,就代表在一段时间内外设也是需要处理多个进程的,所以为了管理好那些进程,每个外设都设定了自己的等待队列。
CPU是不能等待需要访问外设资源的进程的,当一个进程需要访问外设资源时操作系统就会将该进程从CPU的运行队列取下放入外设的等待队列中。
CPU只是一个硬件,它不会做任何管理,一切决策都是由操作系统做好交给CPU执行。
一个进程从CPU的运行队列中取下放入到了硬件的等待队列中,这个进程就从运行状态转变成为了阻塞状态。
这里说的把进程从CPU的运行队列中取下放入到外设的等待队列,是把进程的PCB从运行队列放入到了等待队列中。
就好像你去面试,一面以后hr觉得你小子好像差点意思,于是就把你放入人才池中,如果后续人没招满就继续面你。面试官是拎着你的脑袋把你放来放去的吗?肯定不是啊,他是将你的简历从面试区放到了等待池,而你也从面试者变成了等待者。
切记,管理进程的本质是管理进程对应的PCB
3.挂起状态
外设很慢,但是有大量的进程都需要访问硬件资源,所以就会导致大量阻塞进程的产生,这些阻塞进程短期内不会被CPU所执行,如果都在内存中则会造成大量的内存占用。
当内存中的阻塞进程过多,就可能导致内存不够用。此时操作系统就会将阻塞进程对应的数据和代码暂存到磁盘中,仅保留进程对应的PCB,这种进程状态就被称为挂起状态,而将进程对应的数据暂存到磁盘中,就称为数据的换出(换入就是加载到内存中)。
阻塞不一定挂起,但是挂起一定阻塞。
二.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)休眠状态,T(stopped)暂停状态,t(tracing stop)追踪状态,Z(zombie)僵尸状态
X(dead)死亡状态,虽然好理解,但是一般无法观察到
D(disk sleep)深度休眠,一般只有在高IO的时候才会出现,就连操作系统都无法杀掉处于深度休眠的进程,一旦出现深度休眠,你的服务器就处在崩溃的边缘了,只能通过断电来恢复或者等待进程自己醒来。
1.运行状态
当我的程序中没有出现任何访问外设的请求时,当我运行这个程序,很轻易的就看到了运行状态。
可以看到这个R后面还有一个“+”,这种带加号的是前台进程,前台进程运行期间无法输入指令(指令失效)但是可以用
ctrl + c
退出程序,不带加号的是后台进程,在后台进程运行期间我们可以输入指令(会起效)但无法使用ctrl + c
退出程序,只能使用kill -9+pid
杀掉进程
2.休眠状态(阻塞的一种)
外设的速度远慢于CPU的速度,所以当一个程序需要访问外设资源的时候,这个进程的大部分时间都是在等待外设资源,运行时间可能只有总时间的百分之一,所以当我们查看这类进程状态时一般都是看到休眠状态
3.暂停状态(阻塞的一种)
kill -19 +pid
就可以将让该pid对应的进程陷入暂停状态
4.追踪状态(阻塞的一种)
在Linux使用gdb调试,当一个进程所对应的代码正在被gdb调试时,该进程所对应的状态就是追踪(tracing stop),也是t状态
三.两个特殊的进程
1.僵尸进程(进程的一种特殊状态)
首先我们要清楚的知道,僵尸进程是一个问题。
进程被创建出来是要完成任务的,所以进程在退出的时候不会立即将该进程对应的资源释放掉,而是要保存一段时间等待父进程或者操作系统来读取子进程的返回代码,因为父进程/操作系统需要知道它交子进程的任务,子进程执行的怎么样。
子进程结束以后的返回结果会存放在PCB中,一个进程退出了它对应的数据和代码可以被释放掉,但它对应PCB不会被释放,需要等待父进程读取完PCB中的返回结果以后由父进程来回收这块资源,如果父进程一直不回收,那么就会导致内存泄漏的问题(内存泄漏不但存在于我们编程时手动开辟空间不释放,在系统层面也存在)。
#include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() { pid_t id=fork(); //no error if(id==0) { //child process while(1) { printf("I am a child process,my pid is:%d,my ppid is:%d\n",getpid(),getppid()); sleep(1); } } else { //parent process while(1) { printf("I am a parent,my pid is:%d,my ppid is:%d\n",getpid(),getppid()); sleep(2); } } return 0; }
我已经杀掉了子进程,但是父进程还在运行也没有回收子进程的资源,所以子进程就陷入了僵尸状态(defunct译为死者),我无法通过
kill
命令杀死僵尸进程,因为它本身就已经死了,只是父进程还没有回收对应的资源。一旦父进程回收了资源,这个子进程就从僵尸状态变成死亡状态了。
2.孤儿进程
子进程先终止父进程不回收,子进程就会变成僵尸进程,如果父进程先终止,那么为了防止子进程的资源没人回收,操作系统(1号进程)就会领养该子进程。此时这个子进程就可以被称为孤儿进程。(依旧使用上面的代码)
这里我杀掉父进程以后并没有看到父进程的僵尸状态,这是因为父进程的父进程就是bash,这个父进程的资源被bash快速回收了所以无法看到父进程的僵尸状态。
父进程先于子进程退出的情况是一定存在的,当父进程先于子进程退出时,操作系统为了防止资源浪费(内存泄漏)也必定会领养这个进程,此时这个子进程的父进程就变成了1号进程并且由前台程序变成后台程序。
这种被领养的进程就被称为孤儿进程。
四.进程的优先级
首先要区分优先级和权限的问题,所谓权限就是你能不能的问题;而优先级则是已经确定了能,是先做还是后做的问题。
1.进程优先级的概念
进程优先级的本质是PCB中的一个整数(也可能是几个)
使用
ps -la
可以显示当前用户下的进程信息PRI(Priority)是优先级的意思,默认值是80
NI(nice)默认值是零,Linux支持修改正在运行的进程的优先级,修改进程优先级就是通过修改NI来实现的
进程的优先级=默认优先级(80)+NI值
2.修改NI值
1.修改nice值需要使用sudo + top
,当进入到top之后使用r指令就可以调出修改nice的命令栏
2.nice值的有效范围是[-20,19],也就是说优先级的范围是[60,99],优先级数字越小优先级越高
不要随意的去修改一个进程的优先级,进程的优先级越高抢占CPU资源的能力也就越强,如果人为的去修改进程的优先级使其过高会扰乱调度算法,使操作系统在分配资源的时候失衡。
五.进程切换
进程是计算机学科中最深刻,最成功的概念之一。在现代操作系统上运行一个程序时我们会得到一个假象就好像CPU上只有我这一个进程,它独占了处理器和内存。
我们已经知道CPU总是小于进程个数的,也就是说进程不可能独占CPU资源(所谓独占就是在指必须执行完当前进程再去执行另一个进程)。能给我们造成这个假象的一个重要原因是CPU是一个很快的硬件设备,当CPU需要处理多个进程时,操作系统就会给每个进程分配一定的时间片,当你这个进程的时间片被执行完就会切换成另一个程序,但是CPU太快了即使将所有进程的时间片都跑完可能还花不到一秒钟。
也就是说操作系统给每个进程规定了一次执行的时间长度,当该进程的时间长度被执行完毕以后CPU就会被强行剥夺并分配给另一个进程。
一个进程一次的时间长度被执行完以后这个进程,可能还没被执行完,也就是说这个进程还会被CPU执行。
为了确保该进程在被CPU再次执行的时候,能清楚的知道上次自己被执行到哪里了,就必须要把该进程上次执行时产生的临时数据保存起来。进程在执行时产生的临时数据就被称为该进程的上下文,上下文是内核重新启动一个被抢占的进程所需的状态,我们暂时可以认为进程的上下文被保存在进程对应的PCB中。
将进程的临时数据保存起来被称为上下文保护;当这个进程再次被CPU执行时需要将该进程的临时数据重新加载到内存中,这就是上下文恢复。
而寄存器中的数据会因为进程的不同而不同,当一个进程被切换上来寄存器中的数据也会被更新成为当前进程的数据。也就是说,寄存器是一套共享的硬件,但寄存器中的数据是各进程私有的。进程切换也就会引起上下文的切换。
此外我想说的一点是:CPU中有一个eip寄存器(俗称PC指针),保存了当前正在执行指令的下一条指令的地址,进程能不断往下执行就是因为PC指针在不断更新。
六.进程特性
1.竞争性:因为进程的数量总是多于CPU的,面临少量的CPU资源,各个进程之间必须要竞争。为了高效完成任务以及更合理的竞争资源,就有了进程的优先级
2.独立性:多个进程之间有独立的地址空间,运行期间独享资源互不打扰
3.并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内让多个进程得以同时推进
**4.并行:**多个进程在多个CPU下,同时运行
以上就是本篇文章的全部内容,这种纯理论的东西难学也不好下结论。如果上文中出现有描述不清或者定义(结论)错误的请在评论区指正错误,感谢各位的观看和斧正。