作者简介邹立伟,Linux系统技术专家。目前在腾讯SNG社交网络运营部计算资源平台组,负责内部私有云平台的建设和架构规划设计。曾任新浪动态应用平台系统架构师,负责微博、新浪博客等重点业务的内部私有云平台架构设计和运维管理。为什么有进程优先级?似乎不需要解释太多。毕竟自从多任务操作系统诞生以来,进程占用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-pc0zorro]#nice-n10bash这样,我又打开了一个bash,设置了它的nice值为10,默认情况下,进程的优先级应该继承父进程,这个值一般为0。我们可以通过nice命令直接查看当前shell的nice值:[root@zorrozou-pc0zorro]#nice10比较正常情况:[root@zorrozou-pc0zorro]#exit以当前nice值为10退出bash,和打开一个正常的bash,让我们检查一下它的Nice值:[root@zorrozou-pc0zorro]#bash[root@zorrozou-pc0zorro]#nice0另外,使用renice命令来调整正在运行的进程的nice值,我们也可以使用,比如Top、ps等查看进程nice值的命令,具体方法我就不多说了,大家可以参考相关的manpage。需要注意的是,我在这里使用的是nicevalue这个术语,而不是优先级。nice值虽然不是优先级,但是确实可以影响进程的优先级。在英语中,如果我们形容一个人很好,通常意味着这个人更受欢迎。什么样的人受欢迎?他们通常是谦虚有礼的人。比如,你和一个好人去吃午饭,你点了两份同样的饭,好人通常会说:“你先吃,你先吃!”,这个人不错!但如果另一个迟到,好人就会挨饿。这是什么意思?越善良的人抢资源能力越差,越不善良的人抢资源能力越强。这就是nice值的意义。nice值越低,进程越不nice,抢占CPU的能力越强,优先级越高(作者解释的太形象了,忍不住手动点赞!!)。在原来使用O1调度的linux上,我们也会把nice值称为staticpriority,这基本符合nice值的特点,也就是nice值设置后,除非我们用renice去改变,否则是不变的。优先级的值会在前一个内核的O1调度器上发生变化,所以也称为动态优先级。什么是优先级和实时进程?我们来看看什么是优先级值,是ps命令中看到的PRI值还是top命令中看到的PR值。为了区分这些概念,以后:用nice值来表示NI值,或者称为静态优先级,即通过nice和renice命令调整的优先级;而实际的优先级值代表PRI和PR值,或者称为动态优先级。我们也统一定义了“优先级”这个词的概念,意思是优先级值。在内核中,进程优先级的取值范围是由一个宏定义的,这个宏的名字是MAX_PRIO,它的值为140。而这个值是由另外两个值相加组成的,一个是NICE_WIDTH宏,代表nice值的取值范围,另一个是代表实时进程(realtime)的优先级范围的MAX_RT_PRIO宏。说白了,Linux其实实现了140个优先级范围,取值范围是从0-139。值越小,优先级越高。-20到19的nice值映射到100-139的实际优先级范围。新生成的进程的默认优先级定义为:#defineDEFAULT_PRIO(MAX_RT_PRIO+NICE_WIDTH/2)实际上对应nice值0。一般情况下,任何进程的优先级都是这个值。即使我们通过nice和renice命令调整进程的优先级,它的取值范围也不会超过100-139的范围,除非进程是实时进程,那么它的优先级值会变成100-139的范围0-99。这里隐含一个信息,就是现在的Linux是一个已经支持实时进程的操作系统。什么是实时操作系统?这里不详细解释它的含义和在工业领域的应用。有兴趣的可以参考实时操作系统的维基百科。简单的说,实时操作系统需要保证相关的实时进程在短时间内做出响应,没有长时间的延迟,并且要求中断延迟和进程切换延迟最小。对于这样的需求,一般的进程调度算法无论是O1还是CFS都无法满足,所以内核设计时,将实时进程分别映射到100个优先级,这些优先级都高于普通进程过程。优先级(nicevalue),和实时进程的调度算法也不同,他们使用更简单的调度算法来减少调度开销。一般来说,Linux系统中运行的进程可以分为两类:实时进程和非实时进程。它们之间的主要区别是优先级。所有优先级值在0-99范围内的都是实时进程,所以这个优先级范围也可以称为实时进程优先级,而在100-139范围内的都是非实时进程。在系统中,可以使用chrt命令查看和设置进程的实时优先级状态。先来看一下chrt命令的使用:首先关注显示的Policyoptions部分,我们会发现系统为各种进程提供了五种调度策略。但是这里没有说明的是,这五种调度策略分别用于两种进程。可用于实时进程的调度策略有:SCHED_FIFO、SCHED_RR,用于非实时进程的有:SCHED_OTHER、SCHED_OTHER、SCHED_IDLE。系统整体的优先级策略是:如果系统中有实时进程需要执行,则实时进程优先执行。直到实时进程退出或主动让出CPU后,才会调度非实时进程。实时进程可以指定的优先级范围是1-99。执行一个要实时执行的程序的方法是:[root@zorrozou-pc0zorro]#chrt10bash[root@zorrozou-pc0zorro]#chrt-p$$pid14840'schurrentschedulingpolicy:SCHED_RRpid14840'schurrentschedulingpriority:10可以看到新开的bash已经是实时进程,默认调度策略为SCHED_RR,优先级为10。如果要修改调度策略,添加一个参数:[root@zorrozou-pc0zorro]#chrt-f10bash[root@zorrozou-pc0zorro]#chrt-p$$pid14843'scurrentschedulingpolicy:SCHED_FIFOpid14843'scurrentschedulingpriority:10刚才说了,SCHED_RR和SCHED_FIFO是实时调度策略,只能对实时进程设置。对于所有实时进程,优先级高(即优先级数小)的进程将保证在优先级低的进程之前执行。SCHED_RR和SCHED_FIFO的调度策略只有在两个实时进程的优先级相同时才会起作用。区别正如名字所暗示的那样:SCHED_FIFO是按照先进先出的队列进行调度的。在优先级相同的情况下,谁先执行谁就先被调度,除非它退出或者主动释放CPU。SCHED_RR以循环方式处理多个相同优先级的进程。时间片长度为100ms。这是Linux对实时进程的优先级和相关调度算法的描述。整体简单实用。比较麻烦的是非实时进程,这是Linux上进程的主要分类。对于非实时进程优先级的处理,首先要介绍一下它们相关的调度算法:O1和CFS。什么是O1调度?O1调度算法是在Linux2.6中引入的。Linux2.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可能要等待几百毫秒才能响应,因为当人输入字符时,其他进程的时间片可能没有用完,所以系统不会调度bash进行处理。为了提高IO消耗进程的响应速度,系统会区分这两类进程,动态调整CPU消耗进程降低其优先级,而IO消耗进程将提高优先级以降低花费在CPU消耗进程上的时间。片段的实际长度。已知nice取值范围为-20-19,对应的priority取值范围为100-139。对于一个nice默认值为0的进程,它的初始优先级值应该是120,随着它的不断执行,内核会观察进程的CPU消耗情况,动态调整优先级值。可调范围为+-5。也就是说最高优先级可以自动调整为115,最低为125。这就是为什么nice值被称为静态优先级,优先级值被称为动态优先级的原因。但是这个动态调整的功能在scheduler换成CFS之后就不需要了,因为CFS换成了另一种CPU时间分配方式,这个我们后面会讲到。什么是CFS完全公平调度?O1已经是上一代调度器了,因为它对多核多CPU系统的支持不好,需要在内核函数中加入cgroups等因素,Linux在2.6.23之后开始启用CFS作为一般优先级(SCHED_OTHER)进程的调度方法。在这个重新设计的调度器中,时间片、动静态优先级、IO消耗、CPU消耗等概念都不再重要。CFS采用了一种全新的方式,为上述功能提供了较为完善的支持。其设计的基本思想是:我们要实现一个对所有进程完全公平的调度器。又是那个老问题:如何做到完全公平?答案类似于上一篇IO调度文章中CFQ的思路:如果当前有n个进程需要调度执行,那么调度器应该在一个比较小的时间范围内调度这些进程。所有n个进程都被调度执行一次,他们平分cpu时间,这样所有进程都可以被公平调度。那么这个比较小的时间就是任意一个R态进程被调度的最大延迟时间,即任意一个R态进程在这个时间范围内肯定会被调度响应。这个时间也可以叫做调度周期,它的英文名字是:sched_latency_ns。粮安委的优先事项当然,也需要支持粮安委的优先事项。在新系统中,优先级由时间消耗的速度(vruntime增长)决定。也就是说,对于CFS来说,在vruntime中以同样的方式记录测量时间累积的绝对值,只是不同优先级进程的时间增长速度是不同的。高优先级进程的时间增长缓慢,低优先级进程的时间增长缓慢。时间过得很快。比如一个优先级为19的进程,实际占用cpu为1秒,就会在vruntime中记录1秒。但是如果是-20优先级的进程,那么很有可能实际占用CPU的时间是10s,vruntime中只会记录1s。CFS实际实现的不同nice值的CPU消耗时间比例是按照“每级差CPU使用时间相差10%左右”的原则在内核中设置的。这里的大概意思就是如果有两个nice值为0的进程同时占用cpu,那么他们应该各占用50%的cpu。如果其中一个进程的nice值被调整为1,那么此时应该可以保证一个高优先级的进程比一个低优先级的进程,即nice值为的进程多占用10%的CPU0占55%,nice值为1的进程占45%。然后他们以55:45的比例占用cpu时间。该值的比例约为1.25。即相邻两个nice值的CPU使用率之差应该在1.25左右。内核根据这个原理,对40个nice值做了时间计算比例的对应关系,在内核中以数组的形式存在:什么是多CPU的CFS调度?在上面的描述中,我们可以认为系统CPU中只有一个,那么相关的调度队列也就只有一个。实际情况是系统有多核甚至多CPU。中心一开始就考虑过这种情况。它为每个CPU核心维护一个调度队列,让每个CPU都可以调度自己的队列进程。这也是CFS比O1调度算法效率更高的根本原因:每个CPU都有一个队列,可以避免对全局队列使用大内核锁,从而提高并行效率。当然,这样做最直接的影响就是CPU之间的负载可能不均衡。为了保持CPU之间的负载均衡,CFS需要定期对所有CPU进行负载均衡操作,所以进程可能会出现在不同CPU的调度队列中。开关行为。这个操作的过程还需要对相关的CPU队列进行锁操作,从而降低多个运行队列带来的并行度。但总的来说,CFS的并行队列方式还是比O1的全局队列方式效率更高。尤其是在CPU核心越来越多的情况下,全局锁的效率下降明显加大。***本文的目的是从Linux系统进程的优先级入手。通过对相关知识点的了解,希望大家对系统的进程调度有一个整体的认识。其中,我们还对CFS调度算法进行了较为深入的分析。根据我的经验,当我们观察系统的状态并对其进行优化时,这些知识非常有用。比如使用top命令时,NI和PR值是什么意思?类似的地方还有ps命令中的NI和PRI值,ulimit命令的-e和-r参数的区别等等。当然,希望大家在看完这篇文章后,能够对这些命令的显示有更深入的了解。另外,我们还会发现,top命令中的PR值和ps-l命令中的PRI值虽然意义相同,但是在优先级相同的情况下,显示的值不同。那么,你知道为什么它们显示不同吗?这个问题的答案就留给大家自己去寻找吧!
