共计 6639 个字符,预计需要花费 17 分钟才能阅读完成。
这篇文章将为大家详细讲解有关 Linux 的进程优先级 NI 和 PR 有哪些区别呢,文章内容质量较高,因此丸趣 TV 小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
为什么要有进程优先级?
这似乎不用过多的解释,毕竟自从多任务操作系统诞生以来,进程执行占用 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 值设置,方法是:
[root@zorrozou-pc0 zorro]# nice -n 10 bash
这样我就又打开了一个 bash,并且其 nice 值设置为 10,而默认情况下,进程的优先级应该是从父进程继承来的,这个值一般是 0。
我们可以通过 nice 命令直接查看到当前 shell 的 nice 值:
[root@zorrozou-pc0 zorro]# nice 10
对比一下正常情况:
[root@zorrozou-pc0 zorro]# exit
退出当前 nice 值为 10 的 bash,打开一个正常的 bash,我们查看下其 Nice 值:
[root@zorrozou-pc0 zorro]# bash [root@zorrozou-pc0 zorro]# nice 0
另外,使用 renice 命令可以对一个正在运行的进程进行 nice 值的调整,我们也可以使用比如 top、ps 等命令查看进程的 nice 值,具体方法我就不多说了,大家可以参阅相关 man page。
需要大家注意的是,我在这里都在使用 nice 值这一称谓,而非优先级 (priority) 这个说法。
nice 值虽然不是 priority,但是它确实可以影响进程的优先级。
在英语中,如果我们形容一个人 nice,那一般说明这个人的人缘比较好。什么样的人人缘好? 往往是谦让、有礼貌的人。
比如,你跟一个 nice 的人一起去吃午饭,点了两个一样的饭,先上了一份后,nice 的那位一般都会说:“你先吃你先吃!”,这就是人缘好,这人 nice!但是如果另一份上的很晚,那么这位 nice 的人就要饿着了。
这说明什么?
越 nice 的人抢占资源的能力就越差,而越不 nice 的人抢占能力就越强。这就是 nice 值大小的含义,nice 值越低,说明进程越不 nice,抢占 cpu 的能力就越强,优先级就越高(作者这个解释太形象了,丸趣 TV 小编忍不住要手动点赞!!)。
在原来使用 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。
新产生进程的默认优先级被定义为:
#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,将一个要执行的程序以实时方式执行的方法为:
[root@zorrozou-pc0 zorro]# chrt 10 bash [root@zorrozou-pc0 zorro]# chrt -p $$ pid 14840 s current scheduling policy: SCHED_RR pid 14840 s current scheduling priority: 10
可以看到,新打开的 bash 已经是实时进程,默认调度策略为 SCHED_RR,优先级为 10。如果想修改调度策略,就加个参数:
[root@zorrozou-pc0 zorro]# chrt -f 10 bash [root@zorrozou-pc0 zorro]# chrt -p $$ pid 14843 s current scheduling policy: SCHED_FIFO 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 核心越来越多的情况下,全局锁的效率下降显著增加。
丸趣 TV 小编的目的是从 Linux 系统进程的优先级为出发点,我们也对 CFS 调度算法进行了比较深入的分析。在我的经验来看,这些知识对我们在观察系统的状态和相关优化的时候都是非常有用的。
比如在使用 top 命令的时候,NI 和 PR 值到底是什么意思? 类似的地方还有 ps 命令中的 NI 和 PRI 值、ulimit 命令 - e 和 - r 参数的区别等等。当然,希望看完本文后,能让大家对这些命令显示的了解更加深入。
除此之外,我们还会发现,虽然 top 命令中的 PR 值和 ps - l 命令中的 PRI 值的含义是一样的,但是在优先级相同的情况下,它们显示的值确不一样。
关于 Linux 的进程优先级 NI 和 PR 有哪些区别呢就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。