进程状态
进程状态分有:
运行 新建 就绪 挂起 阻塞 等待 停止 挂机 死亡…
状态其实就是返回的整形,就像某个数字代表运行或者是阻塞等等。
普通操作系统层面理解
首先通过理论来理解操作系统的三大状态。
运行与阻塞
运行
CPU是被动接受进程的,并且操作系统会管理进程并放在内存中让CPU处理。
那么CPU是怎用什么方式去查看所有的进程呢?是定义了一个PCB类型的队列指向第一个进程的PCB,然后进行对所有进程的管理。
这个时候所有的进程是通过数据结构的方式来链接起来的,CPU会一个一个处理进程,这个时候无论被处理还是没被处理都叫做运行状态!
入队的本质就是讲进程的PCB放入CPU的队列中。
注意:一个CPU只有一个运行队列!不是进程正在运行才是运行状态!运行状态用R表示!
阻塞
CPU在处理数据是非常非常快的,对比其他外设来说是这样的。
平时我们的每个进程都与外设有关,也就是说我们CPU在处理进程的时候会发现有些需要外设来配合,但是外设没有CPU快,这就导致了CPU可能还需要等这个进程和外设配合完成才行。
外设也有和CPU一样的队列,但是外设处理的很慢,也就是说会导致排队,那么CPU在处理某个进程发现他需要和外设配合,然后发现这个硬件已经开始排队了,一时半会也轮不到这个进程,也不可能让这个进程一直等着,这样非常影响效率,这个时候操作系统就会让这个进程的PCB在这个外设的队列中进行排队,这个时候R就变成了阻塞状态,等这个进程处理完成了,再去CPU的队列中重新入队,并且阻塞状态重新变为R。
这个也说明,我们的进程不仅仅要占用CPU的资源,也会随时随地占用外设资源。
也就是说,所谓的进程状态不同,也就是进入不同的队列等待资源而已!
挂起与阻塞
现在内存中有一堆的进程,假如说有很多的进程再外设队列,但是现在的内存满了,这应该怎么办呢?
这个时候操作系统就会出手了,将暂时不用的进程先放回磁盘当中,但是保留这个进程的PCB!
这样就节省了一部分空间,因为PCB几乎不占多少空间。
进程被暂时放在磁盘中,这个就叫做挂起,等内存不是那么吃紧了,操作系统就会将这个进程再放回到内存中。
这也侧面说明,阻塞不一定挂起,挂起一定是阻塞。
还有一种很极端的情况就是很多地方很多状态都需要挂起,就跟各种叠BUFF一样。
Linux是怎么做的
首先来看看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 */ };
R代表的就是运行状态:
进程状态这里显示R+,R代表是运行状态,至于后面的+是什么,这个一会说。
如果再代码中加上调用外设会怎么样呢?
答案是会阻塞,S代表阻塞的意思。
如何让一个程序暂停?
先用kill -l查询,之前说过,这个指令是对某个进程发射信号的。
9号是结束进程,19号是暂停进程的运行,18号是继续进程的运行。
继续这个进城之后发现+号没了,然后我对左边的使用了Ctrl+C竟然没有让程序停下来,然后我又用了其他指令发现也能正常运行,只不过刚才的进程也在运行,原来是不可能发生这种情况的。
这是因为+号代表前台进程,可以通过前台关闭,但是+号没了就变成了后台进程,只能通过kill -9 ’PID‘才能让他结束。
这里T代表进程暂停的意思。
上面还有一个D,这个不方便演示,它叫做深度睡眠,只有在进程自动停下来或者是断电的情况下进程才会结束。
比如:
在Linux下,一个进程很大的数据,正在写入磁盘中,但是内存是有限的,如果内存满了,操作系统就会找到这个进程并且干掉他,那么数据就很容易丢失,这个时候就出现了深度睡眠,操作系统看到不会管他,除非这个进程运行完毕自己醒来或者是断电。
想一下,在我们调试一个程序的时候,是会先让他跑起来然后再进行调试的,那么再打断点的时候程序就会停下来:
这里 $@代表第一行:的左边 $@代表:右边。
这里的 t 被称该程序被追踪,这也是一种暂停状态。
X是死亡状态,不方便展示,因为进程结束的一瞬间所有内容都会被回收。
最后一个很重要的进程状态,他是将要死亡的一个进程,也被称为僵尸进程(Z):
一个进程在结束的时候,并不是立刻结束,而是会有返回值结果的,返回的这个结果会放进PCB中,等待父进程来拿走。
但如果没等到父进程来拿走结果,这个进程提前结束了,这时候进程本身被释放掉,但是相对应的PCB没有被释放掉,这就是僵尸进程。.
下面用一个程序来说明:
然后用这个指令来持续监视。
状态从S变成了Z,这时因为创建的子进程直接就结束了,父进程没来得及收回数据!
僵尸进程也是有一定危害的,如果父进程或者是操作系统因为一些原因没办法收回内存中的PCB是会导致内存泄漏的!
孤儿进程
如果父进程比子进程先退出,那么谁来接受子进程的返回值呢?
这种事情很常见,父进程比子进程先结束,这个子进程会被系统进程1号“收养”,这个子进程也就被称为孤儿进程。
我们让父进程结束的时候发现子进程的父进程ID变成了1,这就是系统1号进程,那么为什么父进程结束的时候看不到僵尸状态呢?因为父进程结束PCB直接就会被回收。
并且孤儿进程是后台运行的,原来的S+变成了S。
后台需要用kiil -9 PID 去关闭。
进程优先级
什么是优先级
优先级是什么呢?就是这件事可以做,但是又很多人,谁先来的问题,比如打饭需要排队,谁先来买饭就给谁一个号。
或者是平时的老人优先,军人优先等。
为什么需要分配优先级?因为资源太少了!进程很多,但是硬件很少!
Linux优先级的特点:本质就是PCB的几个整数而已。(就像排队用的号一样)
Linux下用两个整数来确认优先级的:
运行起来之后用这条指令查看状态:ps -al
UID : 代表执行者的身份
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值
进程的优先级 = 老的优先级(80) + nice(NI)
如何改变优先级
Linux中支持正在运行的进程优先级的调整。
改变优先级也就是去改变NI的值而已。
当然平时我们并不需要改变优先级,NI改变的值也是有范围的,所以改变的效果并不太明显。
Ni的范围是:[-20,19]
PRI值越小,优先级越高。
先输入:sudo top
然后按一下r
这里输入的是你进程的PID
这里输入NI值,我输入的是-100
然后q退出
再查看进程的PRI和NI
这里NI只到了-20,然后就PRI也变成了60.
为什么有范围呢?如果某个进程的PRI太小,CPU分配资源就不均衡。
其他概念
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。(例如,你的QQ闪退不会影响微信正常使用,就算是父子进程也不会互相影响,只不过会需要分配更多资源)
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。(大型服务器会有多个CPU,多个进程在不同CPU运行)
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进。(采用进程不断切换,分配一定时间给某个进程运行)
进程切换:
CPU当中有一套寄存器,其中一个寄存器是保存当前进程的PCB,还有一个寄存器(pc/eip)保存的是当前运行指令的下一个指令的地址,对于这个寄存器还有一些寄存器是配合分析计算指令的,还有一些寄存器是保存的都是当前进程产生的临时数据。
当然,一个进程在运行的时候是会产生临时数据的,这个时候会将临时数据放在寄存器上运作(这里要注意寄存器硬件 != 寄存器内部数据,寄存器内部的数据也叫上下文),但是我们知道进程很多,CPU只有一个,所以需要通时间片进行并发。
那么在并发的过程中,当前进程是直接切换到下一个进程吗?不是的,因为现在CPU上还有当前进程的上下文,下一个进程如果直接来,也会产生上下文,直接就会将上一个进程的上下文覆盖掉,所以需要先保存当前进程的上下文在进行切换。
等下次轮到这个进程的时候,再将这个进程的上下文放进CPU就能继续正常工作了。
总结:寄存器中的数据是属于当前进程的!寄存器被所有进程共享,寄存器中的数据是属于每个进程的上下文数据!