Linux操作系统:进程优先级与进程切换(O(1)调度队列)
前言
本章讲述关于进程优先级和进程切换的相关知识
一、 进程优先级
1、是什么:
是CPU资源分配的先后顺序,优先级越高可以优先执行。
2、为什么
可以保证实时相应,比如说交互式进程要高于后台式进程,此时用户不会觉得系统卡死。
确保系统稳定,可以确保系统的核心程序优先于用户程序,防止系统崩溃卡死。
优化公平调度,此时可以防止低优先度进程饥饿问题。
而根本原因是因为,CPU资源有限,而需求或者说任务有轻重缓急。
3、怎么做
PRI(优先级),NI(谦让值)这两个值去协调进程的优先级,一般而言PRI是系统固定的用户不能改动,而NI是可以改动的但是呢有一个范围。
二、PRI&&NI
系统优先级运算公式:static_prio=120+nice。<-(系统优先级用的是它)
ps命令显示优先级:PRI=static_prio-40; <-(这是ps显示的,但其实系统用的不是它)
PRI(priority):值越小,优先级越高,一般系统默认给80(用户不能修改)。
NI(nice):这个值可以修改,范围是-20~19(后文会讲述为什么是这个范围),NI值越高,PRI值越高,表明优先级越低,NI值越低,PRI值越小,优先级越高,0是默认值。
总结一下,真正的优先级看的是PRI,而不是NI,但是NI可以改变PRI。
三、查看优先级的命令
(1)top(动态查看进程)
指令:top

通过标题可以得知,这个得到的进程是一个动态的过程,它会去实时的更新,如果想要退出就点击q即可,同时上边有一圈的头信息。
(2)ps(静态查看)
指令:ps -l或ps -al

通过标题可以得知,这个得到的进程是一个静态的过程,并不会像top一般去实时的更新进程关系之类的。同时我们可以对比二者的图片可以发现,二者的头信息是不一样的,ps这里多了一个UID和PPID,UID是用户ID,表明了这个进程是哪个用户启动的,PPID是父进程的ID。
四、修改优先级
1、创建进程时配置NI(进程创建时)
指令:nice -n %d (文件路径) 注:%d的意思是数字(-20~19)
实例代码 (可执行二进制文件是code.exe)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
while(1)
{
sleep(1);
}
return 0;
}

可以看到此时运行的程序code.exePRI是默认的80(ps显示默认是80,系统基准默认其实是120),NI是默认的0,此时,然后用上边的指令写一下看看发生了什么,

NI此时就变成了10,于此同时PRI变成了90,是因为PRI=80+NI,此时90=80+10,这也就是90的来历,与此同时我们可以看到RPI才是系统看的优先级而NI只是调节的。
2、配置以及存在的进程(进程已经被创建)
指令:renice -n %d -p (进程PID)
同理还是上边的代码,但此时先创建进程然后再改变相应的优先级看看会发生什么。

可以看到NI被修改了同时PRI也被修改了。这里说明一下那个指令-p其实存在与否无所谓,重要的是进程的PID一定得有。
3、top调整
流程:(1) 进入top
(2)按下'r'键,输入要调整的PID,回车。

(3)输入nice值,回车。

这样就修好了,通过ps检查一下。
注意:
普通用户只能把NI调大,也就是只能把优先级升高,root才能调小(-20~19),也就是优先级降低。 同时既然我们可以调大优先级那么也就说明这里有调度算法,这个调度细节后文细讲。
补充概念:(后文需要着重看并发)
(1)竞争性: 系统进程数⽬众多,⽽CPU资源只有少量,甚⾄1个,所以进程之间是具有竞争属性的。为了⾼效完成任务,更合理竞争相关资源,便具有了优先级
(2)独⽴性: 多进程运⾏,需要独享各种资源,多进程运⾏期间互不⼲扰
(3)并⾏: 多个进程在多个CPU下分别,同时进⾏运⾏,这称之为并⾏
(4)并发: 多个进程在⼀个CPU下采⽤进程切换的⽅式,在⼀段时间之内,让多个进程都得以推进,称之为并发
额外补充
为什么用户
五、进程的切换
本质上是,CPU在硬件的中断驱动下,强制剥夺当前进程对CPU的使用权,并保存/恢复硬件上下文的过程。

硬件上下文:是CPU寄存器的一堆数据(注:不是寄存器本身两者不相等)。
压栈:中断发生时,CPU将寄存器的数据压入刚被剥夺使用权的进程内核栈之中。
恢复:将下一个进程的硬件上下文恢复,也就是将这个即将被执行的进程中的内核栈中取出寄存器数据,最后使用中断返回指令切换到这个进程中。恢复硬件上下文时,通常会切换页表,确保访问的是正确的虚拟地址空间。
六、O(1)调度算法
上文提到了关于进程的优先级以及进程的切换,此时就可以学习O(1)调度算法将二者串联起来并且做一个深度的理解。基本逻辑就是CPU从活动队列里获取时间片没有用完的进程,此时把正在运行的时间片用完的进程放到过期队列中,然后执行。当活动队列全部用完之后用swap交换两个队列,此时就又开始了新一轮。
(1) 核心数据结构:两个双向循环链表(活动队列,过期队列)。
active(活动队列):时间片没用完的就放在这里。
expired(过期队列):时间片用完的就放在这里。
下边是关于活动队列和过期队列的部分内容,可以发现二者是一模一样的。

(2)nr_actie:记录当前队列有几个可运行进程
为0则代表着没有可运行的进程,反之则有。如果发现nr_active的情况就会做swap,此时活跃队列变成过期队列,过期队列变为活跃队列。
(3)位图:(找到最高优先级并且保持O(1)的关键因素)
位图:unsigned long bitmap[5] -------0代表着队列不存在进程,1代表着队列里存在进程。
如果说在队列里去找第一个不为空的队列一般需要O(n)的时间复杂度 ,但是如果用位图的话CPU可以用bsf直接找到最低为的1,此时就可以在时间复杂度为O(1)的情况下找到优先级最高的进程。同时bitmap里是5,因为在32位系统下1个unsigned long字节是32位,此时如果5个字节是160位可以满足队列中140个位置的进程标识,如果是4的话就是4*32就是128位,小于140也就不满足队列中140个位置的进程标识。

(4)queue:(存放进程的位置,对应着不同优先级的进程存放)
首先对于这里而言queue这是一个数组并不是一个队列,而每一个下标代表着一个优先级,而这个数组存放的数据内容是链表的头,下边用一张图片可以更好的看清楚。

所以可以看到,数组下标就是上边提到的优先级,与此同时可以看到queue不是队列而是数组,双向链表也不是队列只是说遵循队列的先进先出规则而已。
当找到最高优先级的队列发现里边的进程,此时CPU就会开始执行,当该进程时间片消耗了此时就会产生两种情况,如果是交互式进程,则会将该进程排到活跃队列相同优先级的队尾,如果式非交互式进程就会将该进程放到相同优先级的过期队列里排队。
(5)额外问题汇总
------ 要注意,不能直接修改PRI值
1、修改NI值去修改PRI时用户只能调大不能调小
2、防止用户进程恶意抢占CPU资源,保证系统稳定和公平,防止进程饥饿问题。
3、防止出现位图有进程,而队列无进程的现象。
如果需要修改优先级,可以在当前进程出活跃队列时改nice值,将这个进程修改到优先级更高的过期优先级队列中。
总结
后续将更新命令行参数及环境变量相关内容。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)