linux2.6.29 CFS调度详细分析
众所周知,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.weightideal_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 =4mslinux内核代码中是通过一个叫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增长越慢