众所周知,linux最新的内核采用了CFS的调度机制,网上也有不少文章对CFS调度的源码做了详细的分析,但是大部分的文章太注重细节了,所以没有把CFS的原理进行一下从整体上的概括,基于这个原因,本文要从CFS调度的基本原理以及在公平调度类的整个执行过程为主线来进行详细的说明。
CFS(completely fair schedule),故名思议完全公平的调度,那么它到底怎么实现了完全的公平呢?既然讲公平,那就应该有个评判的标准,在这之前我们先来讲几个比较重要的概念。
调度实体(sched entiy):就是调度的对象,可以理解为进程。
虚拟运行时间(vruntime):即每个调度实体的运行时间。
公平调度队列(cfs_rq):采取公平调度的调度实体的运行队列。
1 每个进程的weight值是如何确定的呢?
上面谈到公平的依据,CFS的公平依据就是每个调度实体的权重(weight),这个权重是有优先级来决定的,即优先级越高权重越高,linux内核采用了nice-prio-weight的一个转换关系来实现了每个调度实体权重的确定。我们来回顾,进程被创建的时候他的优先级是继承自父进程的,如果想改变有优先级,linux内核提供了几个系统调用来改变进程的nice值,从而改变权重,不如sys_nice()系统调用,下面来看一下他们之间的转换关系:
其中,MAX_RT_PRIO=100,nice的值在-20到19之前,那么优先级就在100 - 139之间。
再看prio和weight之间的转换关系,这是个经验公式。通过以上分析我们就可以通过修改nice来修改weight了,这也就回答了每个调度实体的weight是怎么确定的这个问题了吧。
2 基于这些weight,CFS又是怎么来体现公平的呢?
CFS可实现几种不同的公平策略,这些策略是根据调度的对象的不同来区分的。
默认的是不开组调度的公平策略,即调度的单位是每个调度实体。我们来详细看一下是怎么调度的:
假设现在系统有A,B,C三个进程,A.weight=1,B.weight=2,C.weight=3.那么我们可以计算出整个公平调度队列的总权重是cfs_rq.weight = 6,很自然的想法就是,公平就是你在重量中占的比重的多少来拍你的重要性,那么,A的重要性就是1/6,同理,B和C的重要性分别是2/6,3/6.很显然C最重要就应改被先调度,而且占用的资源也应该最多,即假设A,B,C运行一遍的总时间假设是6个时间单位的话,A占1个单位,B占2个单位,C占三个单位。这就是CFS的公平策略。
linux内核采用了计算公式:
l ideal_time = sum_runtime * se.weight/cfs_rq.weight
ideal_time:每个进程应该运行的时间
sum_runtime:运行队列中所有任务运行完一遍的时间
se.weight:当前进程的权重
cfs.weight:整个cfs_rq的总权重
这里se.weight和cfs.weight根据上面讲解我们可以算出,sum_runtime是怎们计算的呢,linux内核中这是个经验值,其经验公式是:
(1) sum_runtime=sysctl_sched_min_granularity * nr_running(if 进程数 > 5)
(2) sum_runtime=sysctl_sched_latency = 20 ms (if 进程数 <= 5)
注:sysctl_sched_min_granularity =4ms
linux内核代码中是通过一个叫vruntime的变量来实现上面的原理的,即:
每一个进程拥有一个vruntime,每次需要调度的时候就选运行队列中拥有最小vruntime的那个进程来运行,vruntime在时钟中断里面被维护,每次时钟中断都要更新当前进程的vruntime,即vruntime以如下公式逐渐增长:
(1) vruntime += delta* NICE_0_LOAD/ se.weight;(if curr.nice!=NICE_0_LOAD)
(2)vruntime += delta; (if curr.nice=NICE_0_LOAD)
在每次更新完vruntime之后,将会进行一次检查,要不要设置调度位TIF_NEED_SCHED,表示要不要被抢占或自动放弃cpu,其实在没有唤醒和CPU之间的进程迁移的时候,只有当前进程主动放弃CPU这种情况,即每个进程都会运行完自己的ideal_time.
即在这里设置抢占位。
通过以上分析,我们基本上已经分析了不开组调度的情况下,进程一般的调度的原理,这里没考虑到唤醒和进程歉意的情况,在后面的文章中会详细的介绍。
至此,我们可能还会有几个疑问:
1 这里只是设置了TIF_NEED_SCHED位,那么谁来检查这个抢占位,来实现进程切换的呢?
这也是在时钟中断里面做的,当时钟中断要返回的时候,会显示的调用schedule()函数,这个函数会检查TIF_NEED_SCHED有没有被置位,来决定是否进行真正的进程切换。
2 还是 A B C这三个进程,如果不考虑唤醒和进程迁移的情况,A的理想的运行时间是3个时间单位,因为只有在
if (delta_exec > ideal_runtime)
resched_task(rq_of(cfs_rq)->curr);
这个时候才设置调度位,那么A运行完这段时间后有没有运行完呢?我们先来仔细分析一下这个公式:
vruntime += delta* NICE_0_LOAD/ se.weight;(if curr.nice!=NICE_0_LOAD)
NICE_0_LOAD是个定值,及系统默认的进程的权值
se,weight是当前进程的权重
delta是当前进程运行的时间
我们可以得出这么个关系:
vruntime与delta成正比,即当前运行时间越长vruntime增长越快
vruntime与se.weight成反比,即权重越大vunruntime增长越慢
现在我们来考虑一种极端的情况:没有唤醒,没有进程迁移,A B C三个进程都是第一次运行
那么系统就会随机从A B C中选一个来运行,因为他们的vruntime第一次是相等的。假设选择B来运行,那么B将运行2个时间单位之后,在某一次时钟中断中发现他的运行时间>它理想运行的时间(runtime>ideal_time),那么将设置TIF_NEED_SCHED位,来进行进程切换;又假设第二次选中了C,那么A运行了稍大于3个时间单位,最后A运行了稍大于1个时间单位。在这种情况下,我们会问,这么运行后A B C有没有运行完啊?(因为我们的理想时间是经验值算出来的),如果没有运行完的话,那下一轮运行又是个什么情况呢?我的理解是经验吧,只有运行的时候才能知道,我们只能感觉上认为应该是对的吧,希望弄明白的同学给我留言!!!
(我想说的是怎么提出一种定量的评价好坏的方法)
3 我们再举一个极端的情况假设有两个用户A,B,注意这里使用户。A用户有1个进程a且a.weight=1;B用户也有1个进程b且b.weight=1000,根据上面的公平理论,我们可以发现B用户可能会一直霸占cpu,在用户更多的情况下,肯能会更糟。为了解决这种问题,CFS引入了组调度,即调度的对象不仅仅限于调度实体,而是可以以用户为调度单位,即A和B位调度单位的话,A B各占50%的CPU。而且只要一个组里的进程被调度,其他的进程也会跟着被调度,但是占用的CPU却至于用户有关。
阅读(657) | 评论(0) | 转发(0) |