现代Linux操作系统是一个多任务并行的操作系统,为了实现这个能力,Linux内核可是煞费苦心,原因是所有算力都在CPU上,而CPU只有几个核心(下面描述只以一个核为例,多核情况请自行扩展),而这么多任务怎么做到并行?该如何分配CPU,这个也就是调度策略要解决的问题;
首先最简单的一个调度策略就是谁先来就先处理谁,简写是 FIFO
假设有3个作业,长度分别代表该作业需要执行的时长
图1
按照FIFO的策略,那么执行的逻辑是优先完成作业2,之后再进行作业1的执行,最后完成作业3,执行如图
图2
这个调度策略逻辑比较简单,不需要过于复杂的计算和调整,但是有个很大的问题,作业2执行没什么问题,作业1因为其本身运行时长比较长,所以整体也可以接受,但是作业3就比较难接受了,比如作业3是一个很简单的字符输入的动作,如果采用FIFO的策略,那么最终展现的情况可能是我们按了键盘然后等待1分钟屏幕才显示字符,这个是现代计算机接受不了的。当然上述是基于体验进行描述的,如果有个指标的话可能更为直观,这个指标就是周转时间,即任务完成时间减去任务到达的时间:
T周转时间 = T完成时间 - T任务到达时间
我们以上面的例子做个假设,为了计算简单我们先假设3个作业同时到达,即T任务到达时间 = 0;
作业 |
运行时间 |
周转时间 |
作业1 |
10s |
10+5-0 = 15(s) |
作业2 |
5s |
5 - 0 = 5(s) |
作业3 |
1s |
10+5+1 - 0 = 16(s) |
平均周转时间为: (15 + 5 + 16)/3 = 12 (s),如果是 对于只需要运行1s的作业3来说,如果对响应时间有要求的确实不太好接受。
所以对于FIFO来说:
其优势在于调度方案简单(这条在某些场景下太重要了);
缺点就是对周转时间要求较高的不太适合;
我们还是继续看上述这个例子,有一种调度情况会得到比较好的平均周转时间,比如作业1、2、3是同时到达CPU,然后执行顺序为作业3、作业2、作业1,
图3
运行演示图如下:
图4
即最短时间的任务优先执行,最后周转时间为:
作业 |
运行时间 |
周转时间 |
作业1 |
10s |
10+5+1-0 = 16(s) |
作业2 |
5s |
5+1 - 0 = 6(s) |
作业3 |
1s |
1 - 0 = 1(s) |
平均周转时间为:(16+6+1)/3 = 7.33(s),所以这也是一种调度策略,即:同一时间内先运行最短的任务,调度策略简写为SJF(Shortest Job First)。从周转时间角度来说,SJF整体要优于FIFO,但是SJF调度策略还有个问题,当3个作业并不是同时到达的情况下(事实情况下也很难做到任务同时到达),那么其周转时间收益有限,比如:
图5
这种情况下执行的顺序依然是作业1、作业2、作业3,周转时间并未好转,可能与FIFO差不多。
还是图5这个例子, 还是使用SJF策略,不过要是如果作业2或作业3来了可以把作业1停掉,换成作业2、作业3执行,等作业2、3执行完成后再继续执行作业1就好了,如下图:
图6
这种策略方式就多出一个概念叫做:抢占,对于上述的FIFO、SJF都是非抢占式的调度策略,也就是系统会将每个任务执行完才会去考虑下一个该执行哪个,而我们图6展示的思路是正在运行任务时,如果来了一个更合适的任务,那么当前正在进行的任务就要释放资源用以执行这个新来的任务,这就是抢占式,这种方式就能够解决SJF调度策略中由于到达时间不同而引发的问题,这种调度策略也有一个新名字,叫做最短完成时间优先策略,简写为STCF(Shortest Tim-t-Completion First)。
看着STCF,想着这个调度策略真好,每次选择时间最优的任务,感觉这个策略是那么完美, 但是一个重要的问题出现了,我哪知道哪个任务是时间最优的呀?虽然可以通过某些特定动作进行一些分类标记,比如磁盘IO、外设输入等,但是依然无法做到任务最优分类,没有了这个前提,STCF看起来似乎也不那么完美了,当然,我们要永远相信Linux内核专家们,为了应对这个问题,一个叫做RR的调度策略应运而生。
RR是什么? RR即Round-Robin,也就是轮转调度,基本思想就是将CPU调度分解成不同的时间片,在同一个时间片内运行一个工作,一个时间片结束后重新进行任务调度,就这样通过时间片轮转的方式进行反复的执行,从而实现相对公平的CPU运行资源分配方案。我们还是以上面图5场景为例作业1,2,3按照到来的先后顺序进行排序
图7
然后对应的CPU分配了一定量的运行时间片进行承载这三个作业,按照作业时长假设作业1需要10个时间片、作业2需要5个时间片、作业3需要1个时间片,那这三个作业需要占用CPU一共10+5+1 = 16个时间片:
图8
开始执行作业时,由于作业1是排在执行队列总最前面的,所以第一个时间片会分配给作业1。第一个时间片执行结束后,作业1就会排到待调度队列的队尾,下一个时间片就会交给作业2,以此类推CPU就会按照时间片分别调度
图9
从上述调度情况可以看出
作业 |
运行时间 |
周转时间 |
响应时间 |
作业1 |
10s |
16(s) |
0s |
作业2 |
5s |
11(s) |
1s |
作业3 |
1s |
3(s) |
2s |
平均周转时间为:(16+11+3)/3 = 10s,从周转时间来看RR并不优秀,但是对于响应时间的提升来说,是非常让人欣喜的,所以这也是RR核心解决的问题,即关注整体响应时间;
综上,简单介绍了目前Linux操作系统中常见的调度方式,从结果来看每种算法都有自身的关注点,同时也存在不足点,所以对于调度算法来说没有明确的好坏之分,只有适合什么业务场景,在不同场景下选择合适的调度算法,会得到不同的业务提升。