刚才说过,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时间分配方式,这个我们后面再说。
再简单了解了O1算法按时间片分配CPU的思路之后,我们再来结合进程的状态简单看看其算法描述。我们都知道进程有5种状态:
S(Interruptible sleep):可中断休眠状态。
D(Uninterruptible sleep):不可中断休眠状态。
R(Running or runnable):执行或者在可执行队列中。
Z(Zombie process):僵尸。
T(Stopped):暂停。