Linux的进程优先级NI和PR
为什么要有进程优先级?
这似乎不⽤过多的解释,毕竟⾃从多任务操作系统诞⽣以来,进程执⾏占⽤cpu的能⼒就是⼀个必须要可以⼈为控制的事情。因为有的进程相对重要,⽽有的进程则没那么重要。
进程优先级起作⽤的⽅式从发明以来基本没有什么变化,⽆论是只有⼀个cpu的时代,还是多核cpu时代,都是通过控制进程占⽤cpu时间的长短来实现的。
就是说在同⼀个调度周期中,优先级⾼的进程占⽤的时间长些,⽽优先级低的进程占⽤的短些。
请⼤家真的不要混淆了系统中的这两个概念:nice(NI)和priority(PR),他们有着千丝万缕的关系,但对于当前的Linux系统来说,它们并不是同⼀个概念。
我们看这个命令:
⼤家是否真的明⽩其中PRI列和NI列的具体含义有什么区别?
毕淑敏我很重要
同样的,如果是top命令:
⼤家是否搞清楚了这其中PR值和NI值的差别?如果没有,那么我们可以⾸先搞清楚什么是nice值。
什么是NICE值?
红狼斑NICE值应该是熟悉Linux/UNIX的⼈很了解的概念了,它是反应⼀个进程“优先级”状态的值,其取值范围是-20⾄19,⼀共40个级别。
这个值越⼩,表⽰进程”优先级”越⾼,⽽值越⼤“优先级”越低。
例如,我们可以通过NICE命令来对⼀个将要执⾏的bash命令进⾏NICE值设置,⽅法是:
1. [root@zorrozou-pc0 zorro]# nice -n 10 bash
这样我就⼜打开了⼀个bash,并且其nice值设置为10,⽽默认情况下,进程的优先级应该是从⽗进程继承来的,这个值⼀般是0。
我们可以通过nice命令直接查看到当前shell的nice值:
1. [root@zorrozou-pc0 zorro]# nice
2. 10
对⽐⼀下正常情况:
1. [root@zorrozou-pc0 zorro]# exit
退出当前nice值为10的bash,打开⼀个正常的bash,我们查看下其 Nice值:
1. [root@zorrozou-pc0 zorro]# bash
2. [root@zorrozou-pc0 zorro]# nice
3. 0
另外,使⽤renice命令可以对⼀个正在运⾏的进程进⾏nice值的调整,我们也可以使⽤⽐如top、ps等命令查看进程的nice值,具体⽅法我就不多说了,⼤家可以参阅相关man page。
需要⼤家注意的是,我在这⾥都在使⽤nice值这⼀称谓,⽽⾮优先级(priority)这个说法。
nice值虽然不是priority,但是它确实可以影响进程的优先级。
在英语中,如果我们形容⼀个⼈nice,那⼀般说明这个⼈的⼈缘⽐较好。什么样的⼈⼈缘好?往往是谦让、有礼貌的⼈。
⽐如,你跟⼀个nice的⼈⼀起去吃午饭,点了两个⼀样的饭,先上了⼀份后,nice的那位⼀般都会说:“你先吃你先吃!”,这就是⼈缘好,这⼈nice!但是如果另⼀份上的很晚,那么这位nice的⼈就要饿着了。
这说明什么?
越nice的⼈抢占资源的能⼒就越差,⽽越不nice的⼈抢占能⼒就越强。这就是nice值⼤⼩的含义,nice值越低,说明进程越不nice,抢占cpu 的能⼒就越强,优先级就越⾼(作者这个解释太形象了,⼩编忍不住要⼿动点赞!!)。
在原来使⽤O1调度的Linux上,我们还会把nice值叫做静态优先级,这也基本符合nice值的特点,就是当nice值设定好了之后,除⾮我们⽤renice去改它,否则它是不变的。
⽽priority的值在之前内核的O1调度器上表现是会变化的,所以也叫做动态优先级。
什么是优先级和实时进程?
我们再来看看什么是priority值,就是ps命令中看到的PRI值或者top命令中看到的PR值。
本⽂为了区分这些概念,以后:
统⼀⽤nice值表⽰NI值,或者叫做静态优先级,也就是⽤nice和renice命令来调整的优先级;
⽽实⽤priority值表⽰PRI和PR值,或者叫动态优先级。
我们也统⼀将“优先级”这个词的概念规定为表⽰priority值的意思。
在内核中,进程优先级的取值范围是通过⼀个宏定义的,这个宏的名称是MAX_PRIO,它的值为140。
⽽这个值⼜是由另外两个值相加组成的,⼀个是代表nice值取值范围的NICE_WIDTH宏,另⼀个是代表实时进程(realtime)优先级范围的MAX_RT_PRIO宏。
最后的冲刺说⽩了就是,Linux实际上实现了140个优先级范围,取值范围是从0-139,这个值越⼩,优先级越⾼。nice值的-20到19,映射到实际的优先级范围是100-139。
新产⽣进程的默认优先级被定义为:
1. #define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2)
藕粉有什么功效与作用实际上对应的就是nice值的0。
正常情况下,任何⼀个进程的优先级都是这个值,即使我们通过nice和renice命令调整了进程的优先级,它的取值范围也不会超出100-139的范围,除⾮这个进程是⼀个实时进程,那么它的优先级取值才会变成0-99这个范围中的⼀个。
这⾥隐含了⼀个信息,就是说当前的Linux是⼀种已经⽀持实时进程的操作系统。
什么是实时操作系统?
我们就不再这⾥详细解释其含义以及在⼯业领域的应⽤了,有兴趣的可以参考⼀下实时操作系统的维基百科。
简单来说,实时操作系统需要保证相关的实时进程在较短的时间内响应,不会有较长的延时,并且要求最⼩的中断延时和进程切换延时。
对于这样的需求,⼀般的进程调度算法,⽆论是O1还是CFS都是⽆法满⾜的,所以内核在设计的时候,将实时进程单独映射了100个优先级,这些优先级都要⾼于正常进程的优先级(nice值),⽽实时进程的调度算法也不同,它们采⽤更简单的调度算法来减少调度开销。
总的来说,Linux系统中运⾏的进程可以分成两类:
实时进程
⾮实时进程
它们的主要区别就是通过优先级来区分的。
所有优先级值在0-99范围内的,都是实时进程,所以这个优先级范围也可以叫做实时进程优先级,⽽100-139范围内的是⾮实时进程。
在系统中可以使⽤chrt命令来查看、设置⼀个进程的实时优先级状态。我们可以先来看⼀下chrt命令的使⽤:
我们先来关注显⽰出的Policy options部分,会发现系统给各种进程提供了5种调度策略。
但是这⾥并没有说明的是,这五种调度策略是分别给两种进程⽤的,对于实时进程可以⽤的调度策略是:SCHED_FIFO、SCHED_RR,⽽对于⾮实时进程则是:SCHED_OTHER、SCHED_OTHER、SCHED_IDLE。
系统的整体优先级策略是:
如果系统中存在需要执⾏的实时进程,则优先执⾏实时进程。
直到实时进程退出或者主动让出CPU时,才会调度执⾏⾮实时进程。
实时进程可以指定的优先级范围为1-99,将⼀个要执⾏的程序以实时⽅式执⾏的⽅法为:
1. [root@zorrozou-pc0 zorro]# chrt 10 bash
2. [root@zorrozou-pc0 zorro]# chrt -p $$
3. pid 14840's current scheduling policy: SCHED_RR
4. pid 14840's current scheduling priority: 10
可以看到,新打开的bash已经是实时进程,默认调度策略为SCHED_RR,优先级为10。如果想修改调度策略,就加个参数:
1. [root@zorrozou-pc0 zorro]# chrt -f 10 bash
2. [root@zorrozou-pc0 zorro]# chrt -p $$
第一的英语怎么说
3. pid 14843's current scheduling policy: SCHED_FIFO
4. pid 14843's current scheduling priority: 10
刚才说过,SCHED_RR和SCHED_FIFO都是实时调度策略,只能给实时进程设置。对于所有实时进程来说,优先级⾼的(就是priority数字⼩的)进程⼀定会保证先于优先级低的进程执⾏。
SCHED_RR和SCHED_FIFO的调度策略只有当两个实时进程的优先级⼀样的时候才会发⽣作⽤,其区别也是顾名思义:
SCHED_FIFO
以先进先出的队列⽅式进⾏调度,在优先级⼀样的情况下,谁先执⾏的就先调度谁,除⾮它退出或者主动释放CPU。
SCHED_RR
美丽的家
以时间⽚轮转的⽅式对相同优先级的多个进程进⾏处理。时间⽚长度为100ms。
这就是Linux对于实时进程的优先级和相关调度算法的描述。整体很简单,也很实⽤。
⽽相对更⿇烦的是⾮实时进程,它们才是Linux上进程的主要分类。对于⾮实时进程优先级的处理,我们⾸先还是要来介绍⼀下它们相关的调度算法:O1和CFS。
什么是O1调度?
O1调度算法是在Linux 2.6开始引⼊的,到Linux 2.6.23之后内核将调度算法替换成了CFS。
虽然O1算法已经不是当前内核所默认使⽤的调度算法了,但是由于⼤量线上的服务器可能使⽤的Linux版本还是⽼版本,所以我相信很多服务器还是在使⽤着O1调度器,那么费⼀点⼝⾆简单交代⼀下这个调度器也是有意义的。管延安
这个调度器的名字之所以叫做O1,主要是因为其算法的时间复杂度是O1。
O1调度器仍然是根据经典的时间⽚分配的思路来进⾏整体设计的。
简单来说,时间⽚的思路就是将CPU的执⾏时间分成⼀⼩段⼀⼩段的,假如是5ms⼀段。于是多个进程如果要“同时”执⾏,实际上就是每个进程轮流占⽤5ms的cpu时间,⽽从1s的时间尺度上看,这些进程就是在“同时”执⾏的。
当然,对于多核系统来说,就是把每个核⼼都这样做就⾏了。⽽在这种情况下,如何⽀持优先级呢?
实际上就是将时间⽚分配成⼤⼩不等的若⼲种,优先级⾼的进程使⽤⼤的时间⽚,优先级⼩的进程使⽤⼩的时间⽚。这样在⼀个周期结速后,优先级⼤的进程就会占⽤更多的时间⽽因此得到特殊待遇。
O1算法还有⼀个⽐较特殊的地⽅是,即使是相同的nice值的进程,也会再根据其CPU的占⽤情况将其分成两种类型:CPU消耗型和IO消耗性。
典型的CPU消耗型的进程的特点是,它总是要⼀直占⽤CPU进⾏运算,分给它的时间⽚总是会被耗尽之后,程序才可能发⽣调度。
⽐如常见的各种算数运算程序。
⽽IO消耗型的特点是,它经常时间⽚没有耗尽就⾃⼰主动先释放CPU了。
⽐如vi,emacs这样的编辑器就是典型的IO消耗型进程。
为什么要这样区分呢?因为IO消耗型的进程经常是跟⼈交互的进程,⽐如shell、编辑器等。
当系统中既有这种进程,⼜有CPU消耗型进程存在,并且其nice值⼀样时,假设给它们分的时间⽚长度是⼀样的,都是500ms,那么⼈的操作可能会因为CPU消耗型的进程⼀直占⽤CPU⽽变的卡顿。姓氏来源
可以想象,当bash在等待⼈输⼊的时候,是不占CPU的,此时CPU消耗的程序会⼀直运算,假设每次都分到500ms的时间⽚,此时⼈在bash上敲⼊⼀个字符的时候,那么bash很可能要等个⼏百ms才能给出响应,因为在⼈敲⼊字符的时候,别的进程的时间⽚很可能并没有耗尽,所以系统不会调度bash程度进⾏处理。
为了提⾼IO消耗型进程的响应速度,系统将区分这两类进程,并动态调整CPU消耗的进程将其优先级降低,⽽IO消耗型的将其优先级变⾼,以降低CPU消耗进程的时间⽚的实际长度。
已知nice值的范围是-20-19,其对应priority值的范围是100-139,对于⼀个默认nice值为0的进程来说,其初始priority值应该是120,随着其不断执⾏,内核会观察进程的CPU消耗状态,并动态调整priority值,可调整的范围是+-5。
就是说,最⾼优先级可以被⾃动调整到115,最低到125。这也是为什么nice值叫做静态优先级,⽽priority值叫做动态优先级的原因。不过这个动态调整的功能在调度器换成CFS之后就不需要了,因为CFS换了另外⼀种CPU时间分配⽅式,这个我们后⾯再说。
什么是CFS完全公平调度?
O1已经是上⼀代调度器了,由于其对多核、多CPU系统的⽀持性能并不好,并且内核功能上要加⼊cgroup等因素,Linux在2.6.23之后开始启⽤CFS作为对⼀般优先级(SCHED_OTHER)进程调度⽅法。
在这个重新设计的调度器中,时间⽚,动态、静态优先级以及IO消耗,CPU消耗的概念都不再重要。CFS采⽤了⼀种全新的⽅式,对上述功能进⾏了⽐较完善的⽀持。
其设计的基本思路是:我们想要实现⼀个对所有进程完全公平的调度器。
⼜是那个⽼问题:如何做到完全公平?答案跟上⼀篇IO调度中CFQ的思路类似:
如果当前有n个进程需要调度执⾏,那么调度器应该在⼀个⽐较⼩的时间范围内,把这n个进程全都调度执⾏⼀遍,并且它们平分cpu时间,这样就可以做到所有进程的公平调度。
那么这个⽐较⼩的时间就是任意⼀个R状态进程被调度的最⼤延时时间,即:任意⼀个R状态进程,都⼀定会在这个时间范围内被调度响应。这个时间也可以叫做调度周期,其英⽂名字叫做:sched_latency_ns。
CFS的优先级
当然,CFS中还需要⽀持优先级。在新的体系中,优先级是以时间消耗(vruntime增长)的快慢来决定的。
就是说,对于CFS来说,衡量的时间累积的绝对值都是⼀样纪录在vruntime中的,但是不同优先级的进程时间增长的⽐率是不同的,⾼优先级进程时间增长的慢,低优先级时间增长的快。
⽐如,优先级为19的进程,实际占⽤cpu为1秒,那么在vruntime中就记录1s。但是如果是-20优先级的进程,那么它很可能实际占CPU⽤10s,在vruntime中才会纪录1s。
CFS真实实现的不同nice值的cpu消耗时间⽐例在内核中是按照“每差⼀级cpu占⽤时间差10%左右”这个原则来设定的。
这⾥的⼤概意思是说,如果有两个nice值为0的进程同时占⽤cpu,那么它们应该每⼈占50%的cpu,如果将其中⼀个进程的nice值调整为1的话,那么此时应保证优先级⾼的进程⽐低的多占⽤10%的cpu,就是nice值为0的占55%,nice值为1的占45%。那么它们占⽤cpu时间的⽐例为55:45。
这个值的⽐例约为1.25。就是说,相邻的两个nice值之间的cpu占⽤时间⽐例的差别应该⼤约为1.25。根据这个原则,内核对40个nice值做了时间计算⽐例的对应关系,它在内核中以⼀个数组存在:
多CPU的CFS调度是怎样的?
在上⾯的叙述中,我们可以认为系统中只有⼀个CPU,那么相关的调度队列只有⼀个。
实际情况是系统是有多核甚⾄多个CPU的,CFS从⼀开始就考虑了这种情况,它对每个CPU核⼼都维护⼀个调度队列,这样每个CPU都对⾃⼰的队列进程调度即可。
这也是CFS⽐O1调度算法更⾼效的根本原因:每个CPU⼀个队列,就可以避免对全局队列使⽤⼤内核锁,从⽽提⾼了并⾏效率。
当然,这样最直接的影响就是CPU之间的负载可能不均,为了维持CPU之间的负载均衡,CFS要定期对所有CPU进⾏load balance操作,于是就有可能发⽣进程在不同CPU的调度队列上切换的⾏为。
这种操作的过程也需要对相关的CPU队列进⾏锁操作,从⽽降低了多个运⾏队列带来的并⾏性。
不过总的来说,CFS的并⾏队列⽅式还是要⽐O1的全局队列⽅式要⾼效。尤其是在CPU核⼼越来越多的情况下,全局锁的效率下降显著增加。
最后
本⽂的⽬的是从Linux系统进程的优先级为出发点,通过了解相关的知识点,希望⼤家对系统的进程调度有个整体的了解。
其中,我们也对CFS调度算法进⾏了⽐较深⼊的分析。在我的经验来看,这些知识对我们在观察系统的状态和相关优化的时候都是⾮常有⽤的。
⽐如在使⽤top命令的时候,NI和PR值到底是什么意思?类似的地⽅还有ps命令中的NI和PRI值、ulimit命令-e和-r参数的区别等等。当然,希望看完本⽂后,能让⼤家对这些命令显⽰的了解更加深⼊。
除此之外,我们还会发现,虽然top命令中的PR值和ps -l命令中的PRI值的含义是⼀样的,但是在优先级相同的情况下,它们显⽰的值确不⼀样。