进程状态概念详解
进程状态
进程状态分为:运行,新建,就绪,挂起,阻塞,等待,停止,挂起,死亡.........
上面的状态都是操作系统的说法!
所以我们要从普遍的操作系统的层面去理解上面的概念!进而去理解linux下面的进程状态的概念!
为什么会有这些状态
首先我们就要对操作系统有个宏观的概念!
==怎么多状态的本质就是为了满足不同的运行场景!==
==上面的所有结构体和数据都是存放在内存里面的!因为操作系统要一开始就被加载在内存中运行!==
运行
因为cpu的核心个数是远少于进程个数的那么进程是如何在CPU里面运行的呢?
阻塞
根据冯诺依曼体系 CPU虽然十分的快!但是外设的速度相比起来就十分的慢了!
==但是进程或多或少的要去访问外设==!例如:我们要想写一个程序想让显示器显示一句话
==而且外设也是少量的!==例如显示器,键盘,显卡这些一般都是只有一个的!
但是访问这些少量外设的往往都不止有一个进程!所以这时候进程就要进入等待!
就绪/新建
进程刚刚被创建出来的状态就是就绪或者新建!(这就是进程的初始状态!)
挂起
从上面我们知道一旦一个进程需要访问外设但是无法立刻访问到的时候!
进程PCB就会进入外设的等待队列变成等待状态!
那么如果有很多个进程同时进入阻塞状态呢?
从上面我们知道
-
我们知道一旦磁盘空出来进程也不会被立刻调度!而是操作系统先把进程状态修改
然后放入CPU的运行队列里面!
-
有大量的阻塞状态的进程那么就意味着要等待很长时间(短期内不会被使用!)
PCB和程序代码都是要占内存的!运行的程序也要占内存
万一存在了大量的阻塞状态的进程导致了内存不够的时候又想要运行新进程应该怎么办?
所以作为调度资源的操作系统 ==操作系统会将这些阻塞的进程的PCB保留在内存但是代码和数据暂时保存在磁盘上!不放在内存里面==这样就可以节省一部分的空间!
==这种将一个进程的代码和数据换出到磁盘中,我们就称该进程被挂起了!==
linux下的进程状态
这就是linux内核中的进程状态
运行——R
int main()
{
while(1)
{
}
return 0;
}
我们在linux下运行一个简单的小程序
查看一下进程
红线画出的便是我们执行的进程! stat就是我们的进程状态!
stat显示 R+ 这就是一个典型的运行状态!
睡眠——S
int main()
{
int a = 0;
int cnt =0;
while(1)
{
a = 1+1;
printf("当前a的值为 %d,cnt = %d",a,cnt++);
}
return 0;
}
我们发现我们加了一行打印后运行状态一下子从R变成立S
这是为什么呢明明左边的代码是一直在跑的!
但是却不是R(运行状态)反倒是S(睡眠状态)
这是因为我们这个程序它访问了显示器——外设!
而我们知道外设是比较慢的!打印数据是很快的!但是等显示器就绪是要花很长时间(相比CPU)所以这个程序就是S状态
==这个程序绝大部分时间都是在等待IO就绪 只有极少部分时间在执行打印代码!==
==所以我们查过去的话有很大概率是S状态!==
==所以大部分进程在访问外设的时候都大概是S状态!==
==这个S状态其实就是linux下面的阻塞状态的一种!==
暂停——T
暂停状态大部分教材会把这个状态归结到挂起和阻塞里面,但是linux下是有这个状态的!
int main()
{
int a = 0;
int cnt =0;
while(1)
{
a = 1+1;
}
return 0;
}
我们发现一开始运行的时候程序就是R状态!
但是我们可以通过linux下面的kill 指令让程序进入T状态!
==此时这个T状态可能被挂起,也可能不被挂起!因为是否挂起是由操作系统决定!==
==但是它是属于阻塞的一种!==
==因为此时没有代码在运行!==
然后我们也可以通过kill指令让代码重新跑起来!
==之所以看不到挂起状态是因为挂起状态完全有操作系统来决定!我们是无法进行干涉的知道了也没有作用!我们也只需要关心进程有没有运行就行!所以一般都是看不到挂起状态的!==
关于+号
我们上面也看到了有的进程状态后面有个+号 而有的后面突然就没有了这是怎么回事?
我们可以看到在有加号的情况下我们在它运行的时候无论我们输入什么都是没有反应的!
程序任然会继续运行!
==这样的进程我们称为前台进程!也就是说一旦这个程序运行了shell命令行就无法获得命令行解析了!除了使用ctrl+c来停止进程!==
然后我们我们尝试暂停它 然后在继续!
我们会发现!此时+号消失了!
然后我们会发现命名行解释器又生效了!
但是此时ctrl+c已经无法停止程序了!
这是因为此时这个进程已经变成了后台进程!
此时只能使用kill -9 命名来杀掉!
深度睡眠——D
S状态其实是一个浅度睡眠!浅度睡眠是可以被ctrl + C进行终止的!
深度睡眠一般是很难看见的!只在高并发高IO的时候常见!
为了方便理解我们先假设有一个进程A 拿着几十万条的用户数据向磁盘写入
磁盘和其他外设不同保存着数据很重要!其他外设可以休眠后随便终止掉!但是磁盘不一样
一般来说当出现大规模的数据IO的时候,一旦出现了内存紧缺,==操作系统首先会去挂起进程,但是一旦连挂起都无法解决的时候!那么操作系统就会自主的杀掉进程!==
==因为内存紧缺导致了进程A被杀掉!但是当磁盘写入数据完毕应该向进程A报告,如果进程A被杀掉了,那么数据就可能发生丢失!==
所以为了防止这种情况!所以有了D状态!这种状态下操作系统即使是内存紧缺操作系统也不会去杀掉进程!防止数据的丢失!
==D状态直白的讲——该状态下的进程,无法被OS杀掉!只能通过断点,或者进程自己醒来来解决!==
我们发现linux下其实没有写什么新建,就绪,阻塞因为这些状态都是以操作系统的角度来进行的一个总结!
像是D ,T ,S都可以被归类到阻塞或者挂起!
像是运行状态在 linux下就表现为R
追踪暂停 ——t
t也是一种暂停的状态!当我们在使用gdb的时候!程序也是暂停的!但是又与一般的暂停有所区别所以独立出来的一种状态!
int main()
{
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
printf("hello debug\n");
return 0;
}
我们可以发现我们调试的程序处于t状态 而我们的gdb处于S状态 因为此时gdb在等待我们进行输入!
==t状态 表示我们的进程现在正在被追踪==
死亡——X
在死亡后进程会被操作系统给回收,因为一旦进程一旦死亡操作系统的回收是十分的快速的!
所以我们一般也看不见死亡状态!
僵尸——Z
==僵尸状态为什存在?==
首先我们要明白 进程被创建 一定是为了完成某种任务!
僵尸进程的危害总结
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎 么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话 说,Z状态一直不退出,PCB一直都要维护?是的!
- 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构 对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
- 内存泄漏?是的!
- Z状态是不能kill指令杀死的!因为Z状态本身就已经是“死”的没法杀死
孤儿进程——S状态
我们上面讲到子进程先退出,然后父进程不退出会导致一个僵尸状态!
那如果反过来!子进程不退出,但是父进程先退出了呢?
那么这个子进程就称为孤儿进程!
但是我们都知道,一个子进程结束后一定要被回收!那么如果父进程已经先结束了,由谁俩回收这个子进程呢?
我们可以用一个例子来演示一下!
#include <stdio.h>
#include<unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0 )
{
while(1)
{
printf("我是子进程 pid = %d ,ppid = %d\n",getpid(),getppid()); sleep(3);
}
exit(1);
}
else
{
while(1)
{
printf("我是父进程 pid = %d ,ppid = %d\n",getpid(),getppid()); sleep(1);
}
}
return 0;
}
当我们运行上面的程序的时候!我们可以看到父进程和子进程都是正常的运行的
接下来我们使用kill 指令杀掉父进程
kill -9
我们可以看到父进程已经被杀死了!但是子进程仍然还在运行!
接下里有几个问题
- 为什么父进程被杀掉之后不存在僵尸状态呢?——因为父进程也有它的父进程!命令行下面的所有进程都是bash的子进程!==父进程被杀掉后直接被bash回收了!==我们能看到子进程的僵尸状态是因为它对应的父进程没有进程回收!
- 我们发现子进程此时的PPID 变成了1——这个1号进程是什么呢?就是操作系统,也就是说当如果不领养那么子进程对应的僵尸进程,就无人回收资源了!
- 系统领养后,这个子进程的+号也消失了,说明这个子进程变成了一个后台进程!——如果前台进程创建的子进程被孤儿了,那么会自动变成一个后台进程!
进程优先级
其实我们一般不怎么关心优先级!优先级一般和调度器有很大关系!linux下我们可以设置优先级,但是很有限。
什么叫做优先级?
linux下还有个概念叫做权限!
那么权限和优先级是一回事吗?不是
权限是决定能不能做!
而优先级是==能做!但是先做还是后做!==
为什么会存在优先级
因为资源太少了!像是我们的CPU一般只有一个或者几个!
但是进程却一般友几十个或者几百个!
所以就得确认优先级
linux下的优先级特点!
计算机里面的优先级的本质就是pcb里面的一个整数数字!(可能是几个!)
linux里面的优先级是用两个整数来代替的!
一个是PRI(priority) 一个是NI(nice)
==最终优先级 = 老的优先级 + nice==
top指令——修改优先级
输入top后就会进入这个页面!——使用top修改可能需要用到root权限!
输入r就会跳出一个renice 后面显示的pid就是当前进程的pid
我们可以输入我们想要进程pid用来修改nice值!
这时候就可以调整nice值了!
一般nice值越小,优先级越高!
我们尝试输一个-100和一个100
==我们可以发现linux下nice值的范围是 从 [-20,19]==
为什么要给一个范围呢?这是因为为了防止调优先级导致某个进程过度占用CPU资源!导致调度失衡!
然后我们将这nice值改成9再看看
我们刚刚说过最终优先级 = 老的优先级 + nice值吗?
刚刚我们的优先级是99 那不该是 108吗 或者再不济应该还是 99啊?
原因是如果老的优先级 就是指原先的优先级的话很容易出问题!
所以linux是将这个老的优先级一律设置成80!这样也方便!
总结
- PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小 进程的优先级别越高
- 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
- PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
- 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
- 所以,调整进程优先级,在Linux下,就是调整进程nice值
- nice其取值范围是-20至19,一共40个级别。
- 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进 程的优先级变化。
- 可以理解nice值是进程优先级的修正修正数据
进程其他相关概念!
竞争性
系统进程数目众多,==而CPU资源只有少量==,甚至1个,所以进程之间是具有竞争属性的。为了高 效完成任务,更合理竞争相关资源,便具有了优先级
独立性
多进程运行,需要独享各种资源,多进程运行期间互不干扰
(像是我们一般开启多个软件,其中一个软件闪退是不会影响剩下的软件的!)
==父子进程也是一样!父子进程也是互相独立的!==
并行
多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发
多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发
进程切换
==首先我们要知道CPU一直在执行三件事——1.取指令 2.分析指令 3.执行指令==
CPU里面存在很多寄存器——这些寄存器都是同一套的!
当CPU要执行某个进程的时候!首先会将进程的PCB放到寄存器里面!然后通过PCB来找到该进程的代码!