【linux】linux进程概念(三)(进程状态,僵尸进程,孤儿进程,进程优先级)
小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
linux系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
目录
- 前言
- 一、操作系统学科上的进程状态
- 运行状态
- 阻塞状态
- 挂起状态
- 二、linux中对应的进程状态
- R运行状态
- S睡眠状态
- D磁盘休眠状态
- T停止状态
- 三、僵尸进程(Z僵尸状态)
- 四、孤儿进程
- 查看1号进程
- 五、批量化操作
- 六、进程优先级
- 基本概念
- 如何调整优先级
- PRI和NI
- 具体调整优先级
- 使用nice和renice
- 使用top
- 总结
前言
【linux】linux进程概念(二)——书接上文 详情请点击<——
本文由小编为大家介绍——【linux】linux进程概念(三)
本文的讲解是基于linux进程概念(二)的基础上进行讲解,读者友友在阅读本文前请先阅读linux进程概念(二),即点击后面的蓝色字体链接进行阅读 详情请点击<——
一、操作系统学科上的进程状态
上述为操作系统学科上的进程状态,这里小编仅针对进程状态特定的介绍一下运行,阻塞,挂起三种状态
运行状态
- 一个CPU对应一个运行队列,这个运行队列的结构体中有两个进程控制块PCB的指针,指向PCB队列的头和尾,
- 这个运行队列其实是大量的进程在排队,排队其实是进程的PCB去排队,PCB中会使用指针指向其对应进程的代码和数据,虽然名称叫做运行队列但并不是所有的进程都正在运行,运行指的是处于运行队列中的进程已经准备好可以被调度器随时进行调度去运行
- 处于队列头上的PCB会被优先调度,当头部的进程PCB被调度到CPU上运行片刻后会重新被调度器拿下来放到队列的尾上继续排队,这个把大量进程从CPU拿上去,放下来的动作叫做进程切换
- 我们知道CPU的运行速度是非常快的,运行速度是纳秒级别,不能以人的常规思维去看待CPU的运行速度,以我们的电脑为例,上面可以同时运行许多应用软件,例如网易云,浏览器,画图等,这些软件的运行其实就是进程的运行,我们人的肉眼看到这些进程在同时运行,给我们一种错觉是认为这些进程是在CPU上同时跑的
- 事实其实不是这样的,由于CPU的运行速度非常快,进程是一个一个从进程队列的头上被调度器调度到CPU上去运行的,运行片刻后就被拿下来放到运行队列的尾继续排队,即CPU同一时刻之只能执行一个进程,CPU的运行速度非常快,同时在进程PCB中还有时间片信息,这个时间片限定了进程在CPU上执行的时间,例如时间片的时间对应的是10ms,所以不可能出现一个进程一直在占用CPU的资源去执行直到进程的结束,在这一个时间段内,由于CPU的运行速度非常快,运行队列经过调度器调度会被CPU反复循环跑上N次,即出现了在一个时间段内,所有进程的代码都被执行,这个过程叫做并发执行
- 处于运行队列的进程是运行态,简称R(run)状态
阻塞状态
- 我们知道计算机的组成中有各种设备,即外设,操作系统是一款进行软硬件资源管理的软件,那么其对于对于设备的管理是采用的先描述再组织,即使用DCB设备控制块strcut dev对每一个设备进行描述为DCB设备控制块的对象,然后再使用一定的数据结构将这些对象链接起来,例如使用单链表将这些对象进行链接便于进行管理
- 同时在这些DCB设备控制块中有PCB结构体指针,当进程需要访问外设例如键盘时,键盘并没有准备好,这里指的是用户没有敲键盘输入数据,即此时键盘的状态是未准备好,即关闭状态,那么此时进程由于要从键盘中读取数据就只能等待,那么进程控制块PCB就会被链接到DCB中的进程控制块PCB指针上,形成队列,并且在操作系统学科上给这个队列起名阻塞队列,当这个进程在等待时,如果接下来还有进程也需要访问键盘,读取键盘的数据,就只能等待,即链接到阻塞进程最后一个进程的后面继续进行等待,那么此时阻塞队列中的进程的状态就为阻塞状态
挂起状态
- 在某些情况下,当操作系统内部的内存资源严重不足的时候,因为操作系统是款进行软硬件资源管理的软件,它要对上提供良好的环境,这个良好指的是安全,稳定,高效,所以当内存严重不足的时候,操作系统必须节省出内存,保证程序的正常运行
- 那么如何节省呢?由于计算机存在各种设备,即外设,在计算机上的操作大多数都要转化为对某种外设的访问,外设不一定准备好,即外设的状态不一定打开,那么也就会出现大量进程排队等待外设的情况,即有相当多的进程处于阻塞状态,操作系统就会从处于阻塞状态的进程下手
- 通俗来讲,一个已经加载到内存中的程序叫做进程,细致来讲,那么进程是由内核数据结构PCB和对应的代码和数据组成,那么一个进程存在,其对应的代码和数据已经被加载到了内存,这个代码和数据要占用一定的内存,当存在大量的进程,那么就会有大量的内存和数据被加载到内存中,同时还会有大量的进程处于阻塞状态,阻塞状态不同于运行状态,阻塞状态是进程正在等待,这个等待的过程其实仅仅让进程的PCB进行等待即可,因为进程当前处于阻塞状态,没有办法从外设中读取数据,即无法运行,所以即使代码和数据不在内存中也没有关系,磁盘的空间相比较于内存非常大,那么操作系统会将处于阻塞状态进程的代码和数据放到磁盘的交换区,这个过程叫做换出,也就是进程的挂起,那么进程不会一直处于阻塞状态,外设也有可以进行读取数据的状态,即打开状态,那么进程等待结束,可以从外设中进行读取数据,那么此时操作系统会将处于磁盘中进程的对应的代码和数据拿回来,即换入,进程那么就可以从外设中读取数据,将进程链入CPU的运行队列中,此时进程处于运行状态,就可以正常运行了
- 这种操作系统的内部资源严重不足的情况一般比较少,所以进程的挂起并不是都会有的,只有当操作系统内部的内存资源严重不足的时候,操作系统才会将进程挂起,并且挂起还会有其它形式其它触发条件,例如开发人员调试需求,系统管理操作,进程优先级策略等
二、linux中对应的进程状态
//linux源代码如下
static const char * const task_state_array[] = {
"R (running)" //运行
"S (sleeping)" //睡眠
"D (disk sleep)" //深度睡眠
"T (stopped)" //停止
"t (tracing stop)"//追踪停止
"X (dead)" //死亡
"Z (zombie)", //僵尸
};
Z死亡状态:这个状态只是一个返回状态,并不会在任务列表中看到这个状态,当Z僵尸状态结束后就会进入Z死亡状态
R运行状态
R运行状态(running):并不意味着进程一定在运行中,它表明进程要么在CPU运行中,要么在CUP对应的运行队列里
- 那么小编编写一个死循环判断程序,用于讲解进程的运行状态
#include
int main()
{
while(1)
{}
return 0;
}
运行结果查看如下
- 当我们将代码运行起来之后程序陷入死循环判断
- 当我们使用ps工具查看进程状态,查看到STAT即state状态那一栏我们的进程对应的为R+,即前台运行状态,当状态中没有+的时候表示后台的某种状态,此时我们的进程就是R运行状态
- 思考一下为什么我们的进程处于运行状态?因为我们的死循环判断程序会不断的进行重复执行代码,占用CPU资源,并且它不满足访问外设,例如显示屏,键盘等,进入阻塞状态或其它状态的情况,所以此时进程就处于运行状态
S睡眠状态
S睡眠状态(sleeping):意味着进程在等待事件完成,同时也叫做可中断睡眠,浅度睡眠,即睡眠期间可以被唤醒,对应操作系统学科中的阻塞状态
- 这是一个死循环打印的程序
#include
int main()
{
while(1)
{
printf("i am a process
");
}
return 0;
}
运行结果如下
- 当小编将程序运行起来后,我们的程序在屏幕上疯狂的进行打印
- 当我们使用ps工具查看进程状态,查看到STAT即state状态那一栏我们的进程对应的为S+,此时我们的进程就是S睡眠状态
- 那么思考为什么我们的进程会处于睡眠状态?由于我们的程序会在屏幕上疯狂打印,那么就代表我们的进程要不断访问外设,即访问显示屏,在显示屏上进行写入数据,我们知道CPU的运行速度很快,进程运行的写入很快,由于进程运行的太快了,程序写入运行的那一瞬间是运行状态,我们使用ps很难捕捉到,并且显示屏的显示速度没有那么快,即显示屏并不是时时都是处于可写入状态,即打开状态,那么我们的进程就要不断的等待外设,即显示屏,此时就对应操作系统学科的阻塞状态,进程要链入显示屏的阻塞队列进行等待,所以此时我们的进程处于休眠状态
- 如下,小编编写了一个scanf读取程序
#include
int main()
{
int a = 0;
scanf("%d", &a);
return 0;
}
运行结果如下
- 当小编将程序运行起来后,我们的程序在屏幕上不断等待用户敲击键盘去写入数据
- 当我们使用ps工具查看进程状态,查看到STAT即state状态那一栏我们的进程对应的为S+,此时我们的进程就是S睡眠状态
- 那么思考为什么我们的进程会处于睡眠状态?我们的程序需要从键盘上读取数据,那么进程就是不断等待外设即键盘准备好,此时我们不去敲击键盘输入数据,那么键盘就会一直不准备好,即键盘状态是关闭,那么此时进程就会在键盘设备的阻塞队列中等待,此时就对应操作系统学科的阻塞状态,所以此时我们的进程处于休眠状态
D磁盘休眠状态
D磁盘休眠状态(disk sleep):也叫做深度睡眠,处于这个状态的进程通常会等待IO的结束,不响应任何请求,同时其也对应操作系统学科上的阻塞状态的一种特殊情况,处于深度睡眠的进程,不响应操作系统的任何请求,也无法被 kill -9杀死
- 那么小编以进程写入磁盘数据为例进行讲解
- (该点的讲解为假设情况下,并不是真实的)当我们的进程要向磁盘中写入数据(假设磁盘的内存空间不足以写入该数据),那么磁盘拿到数据进行写入,此时进程等待磁盘数据写入数据是否成功,即等待磁盘的反馈,恰好此时操作系统的内存资源严重不足,需要节约出内存空间,看到了这个进程正在等待,那操作系统看到,心想:“我的内存资源都要严重不足了,系统都要卡死了,你还在这里悠哉游哉的等待”,此时操作系统就将这个进程给杀死了,那么当磁盘尝试写入后,发现磁盘空间不足以写入该进程的数据,那么磁盘心想,这个数据太大了,反正我没有办法写入成功,但是我剩余的空间还可以写入其它较小的数据,那么我就将该进程对应的数据全部丢弃去完成其它进程对应的较小的数据的写入,那么丢弃了,磁盘回头一看向给该进程一个回应,该进程呢?进程没有了
- 那么此时进程被操作系统杀死了,那么进程中对应的代码和数据也一并被释放了,同时磁盘也写入数据失败,将数据直接丢弃了,那么此时就造成了数据丢失问题,那么小编分析一波,操作系统是为了节约内存,维持良好的环境而杀死的进程,这没毛病,进程在等待磁盘的回应是被杀的,进程是受害者,磁盘放不下数据,将数据直接丢失了去完成其它进程对应的较小的数据的写入了,这也没毛病,这是不是很难评判是谁的责任,所以对于这种数据丢失情况无法评判是谁的责任,那在用户使用系统的时候数据肯定不能够频繁的丢失,如果数据频繁的丢失,那造成的后果是不可想象的
- 所以针对这种情况,那么会将该进程设定为深度睡眠,即对于操作系统的不响应任何请求,那么进程不能被操作系统杀死,那么进程对应的代码和数据也不会丢失,只有接收到磁盘对应的回应才会响应苏醒,那么此时再遇到上面这种情况数据就不会丢失了
T停止状态
T停止状态(stopped/tracing stop):可以通过发送信号停止进程,这个被停止的进程也可以通过发送信号来继续运行,在当前阶段的学习中,stopped/tracing stop一般不做区分
tracing stop(即追踪暂停)指调试过程中的进程暂停,例如使用gdb调试器的过程中就对应追踪暂停状态,使用gdb对程序进行调试,那么到断点处,进程会暂停下来,那么gdb就可以控制进程,此时gdb就可以对当前程序的变量等信息进行查看,此时进程所处的状态就是tracing stop追踪暂停状态
T暂停状态有助于我们对进程进行一定的控制,追踪暂停调试
- 使用如下代码演示控制进程
#include
#include
#include
int main()
{
while(1)
{
printf("i am proc,my PID:%d,my PPID:%d
",getpid(),getppid());
sleep(6);
}
return 0;
}
同时还可以使用kill指令发送信号进行控制进程
- kill -19 进程标识符 将进程暂停
- kill -18 进程标识符 让进程继续运行
- kill -9 进程标识符 杀死进程,终止进程
- 那么我们让我们的死循环打印的进程运行起来之后使用ps指令查看进程状态是S+休眠状态(前台运行),这是正常的,因为进程大部分时间都在等待设备,即等待显示屏准备好,具体原因小编在S随眠状态中有讲
- 那么接下来程序运行起来之后就死循环打印,那么接下来小编右边使用kill -19 进程标识符,将进程暂停下来,观察左边程序运行情况,进程就被stopped暂停下来了,此时在右边使用ps查看进程的状态就为对应的T停止状态
- 那么接下来小编在右边使用kill -18 进程标识符,让进程继续运行,观察左边程序运行情况,进程就被继续运行了,此时在右边使用ps查看进程的状态就为对应的S休眠状态(后台运行)
- 那么接下来小编在右边使用kill -9 进程标识符,杀死进程,观察左边程序运行情况,我们的进程就被杀死,即终止运行了
- 使用如下代码演示使用gdb调试进程
#include
#include
#include
int main()
{
printf("i am proc,my PID:%d,my PPID:%d
",getpid(),getppid());
return 0;
}
使用gdb调试进程,查看进程对应的t追踪停止状态
- 我们的makefile中的proc的依赖方法要带-g选项才可以对程序进行调试
【inux】linux项目自动化构建工具-make/Makefile 详情请点击<——
- 使用gdb调试代码,在第8行设置断点,使用r将程序运行到断点处
【linux】linux调试器-gdb的使用 详情请点击<——
- 当程序运行到第8行的断点处之后,使用ps查看进程状态,进程对应进程状态为t,即当前进程处于追踪暂停状态
三、僵尸进程(Z僵尸状态)
僵尸进程(zombies):子进程退出的时候,如果父进程没有主动回收子进程的信息,那么子进程会让自己一直处于Z僵尸状态,即对应子进程相关资源尤其是task_struct结构体不能释放
exit系统调用接口可以终止一个进程,使用exit可以保证我们的子进程或父进程被终止,带来更好的演示
使用exti时不要忘记包含头文件 #include和 #include
- 使用如下进行演示子进程的僵尸进程状态
#include
#include
#include
#include
int main()
{
printf("i am a proc,my PID:%d,my PPID:%d
",getpid(),getppid());
sleep(3);
pid_t id = fork();
if(id > 0)
{
while(1)
{
printf("i am a father,my PID:%d,my PPID:%d
",getpid(),getppid());
sleep(2);
}
}
else
{
int cnt =3;
while(cnt--)
{
printf("i am a child,my PID:%d,my PPID:%d
",getpid(),getppid());
sleep(2);
}
exit(0);
}
}
运行结果如下
- 当执行程序后,初始时只有一个当前进程,使用ps查看当前进程处于S睡眠状态
- 当3秒后,当前进程调用了fork会创建子进程,当前进程成为父进程,子进程和父进程通过if分流,进入不同的执行流去执行不同的代码块,使用ps查看此时子进程和父进程都是处于S睡眠状态
- 父进程会一直死循环间隔睡眠2s打印进程的PID和PPID
- 子进程会打印三次进程的PID和PPID后被exit系统调用终止进程,那么此时父进程忙于打印,没有对子进程做任何事情,即父进程没有主动对子进程的信息进行回收,所以使用ps查看子进程此时就会维持Z僵尸状态
- 同时当小编使用ctrl+c将左边进程强制终止之后,终止的是父进程,那么父进程会被对应的bash进行回收,那么子进程在父进程结束的时候,那么子进程就没有父进程了,瞬间子进程就被1号进程收养了,由于子进程也是处于退出状态还维护着进程信息,那么就会被1号进程回收,进行资源的释放,那么具体原因请见下文讲解的孤儿进程
僵尸进程的危害
- 子进程的退出状态必须被维持下去,因为它要告诉关心它的父进程,父进程交给自己的任务,自己完成的怎么样了,可是如果父进程一直不进行读取,那么此时子进程就要一直维持这个Z僵尸状态
- 那么这个退出状态维护的其实是子进程的进程信息,即进程的PCB即task_struct结构体,那么子进程对应的未进行释放的task_struct结构体会一直会得不到释放,就会一直占用空间,那么就会造成内存泄露
四、孤儿进程
孤儿进程:父子进程中,父进程先退出,子进程的父进程会被改为1号进程(操作系统),我们称该子进程被操作系统领养了,那么像这种父进程是1号进程的子进程我们称为孤儿进程
此时这个子进程就变成了后台进程,普通的ctrl+c无法进行退出,只能使用kill -9 进程标识符,将子进程杀死
#include
#include
#include
#include
int main()
{
printf("i am a proc,my PID:%d,my PPID:%d
",getpid(),getppid());
sleep(3);
pid_t id = fork();
if(id > 0)
{
int cnt=2;
while(cnt--)
{
printf("i am a father,my PID:%d,my PPID:%d
",getpid(),getppid());
sleep(2);
}
exit(0);
}
else
{
while(1)
{
printf("i am a child,my PID:%d,my PPID:%d
",getpid(),getppid());
sleep(2);
}
}
return 0;
}
运行结果如下
- 当执行程序后,初始时只有一个当前进程,使用ps查看当前进程处于S+睡眠状态(前台进程)
- 当3秒后,当前进程调用了fork会创建子进程,当前进程成为父进程,子进程和父进程通过if分流,进入不同的执行流去执行不同的代码块,使用ps查看此时子进程和父进程都是处于S+睡眠状态(前台进程)
- 子进程会一直死循环间隔睡眠2s打印进程的PID和PPID
- 父进程会打印两次进程的PID和PPID后被exit系统调用终止进程,那么此时子进程的父进程的进程信息被bash回收释放了,那么此时子进程也要退出呀,子进程也要被父进程回收进程信息呀,但是却没有了父进程,这里的bash进程算的上是当前子进程的爷爷进程,可是任何一个父进程只对子进程负责,即bash使用fork创建出来的当前子进程的父进程, bash只对当前子进程的父进程负责,bash的代码逻辑中没有要为当前子进程负责的逻辑,所以bash不能回收当前的子进程
- 操作系统肯定不能看下去这样的事情发生,因为任何一个进程都要退出,也要被释放,子进程没人回收这可不行,那么此时操作系统会成为子进程的父进程,即操作系统收养了当前的子进程
- 注意此时的子进程被操作系统收养,在后台运行,并且小编写的是死循环打印,那么子进程不会主动退出,那么只能使用kill -9 进程标识符,杀死子进程进行退出
查看1号进程
- 那么我们使用top指令显示一下linux的进程信息,那么可以看到1号进程对应的就为systemd(在有些较老版本上的操作系统上对应的为init)即操作系统(OS)
- 接下来使用ps指令查看一下systemd(操作系统OS),那么其对应就为1号进程
五、批量化操作
六、进程优先级
基本概念
优先级:对于资源的访问,谁先访问,谁后访问
进程的优先级:CPU资源分配的先后顺序,优先级高的进程有优先执行访问权限的权利
- 为什么要有优先级:因为CPU的资源是有限的,进程有多个,所以就进程之间就必然有竞争关系
- 操作系统必须保证大家良性竞争,所以就要确定优先级
- 如果非良性竞争,那么就会导致某些进程长时间得不到CPU资源,该进程的代码长时间得不到推进——进程的饥饿问题
- 通常来讲,非必要情况用户不需要调整优先级,进程的优先级的调整由调度器管控
如何调整优先级
- 小编使用如下死循环打印程序,讲解如何调整进程的优先级
#include
#include
#include
int main()
{
while(1)
{
printf("i am process,my PID:%d,my PPID:%d
",getpid(),getppid());
sleep(2);
}
return 0;
}
运行结果如下
- 运行程序后,进程不断在屏幕上打印,那么我们复制SSH渠道,在复制的SSH渠道中,使用ps -l查看当前运行进程,发现没有我们的程序proc,这是因为ps -l只会在当前SSH渠道中显示当前运行的进程,对于其它SSH渠道会默认不显示
- 那么这时候我们使用ps -al即,显示全部当前运行进程,即可查看到我们的运行的程序proc
那么观察一下
- PID为进程标识符,PPID为父进程标识符
- UID表示执行者身份
- PRI表示这个进程可被执行的优先级,这个PRI的值越小代表越早被执行
- NI表示这个进程的nice值,表示这个进程优先级的修正数据
PRI和NI
PRI表示这个进程可被执行的优先级,这个PRI的值越小代表越早被执行
NI表示这个进程的nice值,表示这个进程优先级的修正数据
- PRI表示越小就越快被执行,那么加入NI(nice)值后会变成PRI(新)=PRI(旧)+NI,注意这个旧的PRI为初始值一直不变,比如初始值PRI为80,NI为10,那么运算后,新PRI为80+10为90,那么下次计算时,PRI仍然使用初始值进行计算,比如下一次NI为-10,那么运算后,新PRI为80-10为70
- 这样NI(nice)为负数,那么那么该程序优先级的值的会变小,即进程优先级会变高,也就越快被执行
- 所以在linux中,用户调整进程优先级的方式是调整nice值
- 那么既然我用户可以调正优先级,那么我可不可以将nice设置的非常小,然后大大的提高我们进程的优先级,进而达到控制优先级,让我们的进程可以达到几户一直被调度呢?
- 不可以,linux本身是有调整优先级的调度器的,调度器决定哪一个进程被优先调度,即由调度器确定进程的优先级,调度器使用调度算法极为合理的安排进程的优先级,提高系统性能,所以linux不想让用户过多的参与优先级的调整,所以限定我们在一定范围进行调正, 即nice值对应[-20,19],一共40个值,那么当PRI为80时,那么对应优先级就为[60,99]
具体调整优先级
进程优先级的调整需要使用su将普通用户切换到root用户但不改变当前路径【linux】su的使用
或使用sudo提权也可以【linux】如何才能让普通用户进行sudo提权
小编采用su和sudo的方式共同进行讲解
使用nice和renice
- nice调整当前bash的nice值,那么bash创建出来的子进程的nice值都会遵循bash的nice值
- 使用方法nice -n 你要调整的nice值 bash
运行结果如下
- 那么调整后运行我们的程序proc,使用ps -al在复制的SSH渠道中查看程序其对应的NI(nice)值就为10,那么使用ps -al查看对应的PRI对应也为80+10即为90,nice在程序运行前的SSH渠道中使用不需要sudo,可以直接提权
- rename的对一个正在运行的程序使用,可以调整其nice值
- rename -n 要设置的nice值 -p 进程标识符
运行结果如下
- 那么小编在复制的SSH渠道中使用renice对小编当前运行的nice值进行了调整为了0,使用ps -al进行查看那么对应的PRI就为80+0等于80
- 同时也可以直接使用nice以一个nice值运行一个进程,此时后面需要跟路径
- nice -n 设置的nice值 路径,这里小编以当前路径下的进程proc运行
- 那么使用nice以一个nice值运行一个进程
- 那么小编在复制的SSH渠道中使用ps -al查看对应的proc进程的nice值就为19,其PRI值就为80+19即为99
使用top
- 将程序运行起来,复制SSH渠道,在复制的渠道中输入top,按下回车
- 之后继续输入r,按下回车
- 那么此时它会提示要你输入要调整进程对应的PID,将我们进程的PID输入18888,按下回车
- 继续输入要调整的nice值,这里输入10,按下回车
- 那么使用ps查看,我们的进程的nice值就被调整为10,其对应的PRI就对应为80+10即为90
总结
以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!