/* 15 */119304647,148102320,186737708,238609294,286331153,
};
具体计算细节不在这里细解释了,有兴趣的可以自行阅读代码:kernel/shced/fair.c(Linux 4.4)中的__calc_delta()函数实现。
根据CFS的特性,我们知道调度器总是选择vruntime最小的进程进行调度。那么如果有两个进程的初始化vruntime时间一样时,一个进程被选择进行调度处理,那么只要一进行处理,它的vruntime时间就会大于另一个进程,CFS难道要马上换另一个进程处理么?出于减少频繁切换进程所带来的成本考虑,显然并不应该这样。CFS设计了一个sched_min_granularity_ns参数,用来设定进程被调度执行之后的最小CPU占用时间。
[zorro@zorrozou-pc0 ~]$ cat/proc/sys/kernel/sched_min_granularity_ns
2250000
一个进程被调度执行后至少要被执行这么长时间才会发生调度切换。
我们知道无论到少个进程要执行,它们都有一个预期延迟时间,即:sched_latency_ns,系统中可以通过如下命令来查看这个时间:
[zorro@zorrozou-pc0 ~]$ cat/proc/sys/kernel/sched_latency_ns
18000000
在这种情况下,如果需要调度的进程个数为n,那么平均每个进程占用的CPU时间为sched_latency_ns/n。显然,每个进程实际占用的CPU时间会因为n的增大而减小。但是实现上不可能让它无限的变小,所以sched_min_granularity_ns的值也限定了每个进程可以获得的执行时间周期的最小值。当进程很多,导致使用了sched_min_granularity_ns作为最小调度周期时,对应的调度延时也就不在遵循sched_latency_ns的限制,而是以实际的需要调度的进程个数n * sched_min_granularity_ns进行计算。当然,我们也可以把这理解为CFS的”时间片”,不过我们还是要强调,CFS是没有跟O1类似的“时间片“的概念的,具体区别大家可以自己琢磨一下。
新进程的VRUNTIME值CFS是通过vruntime最小值来选择需要调度的进程的,那么可以想象,在一个已经有多个进程执行了相对较长的系统中,这个队列中的vruntime时间纪录的数值都会比较长。如果新产生的进程直接将自己的vruntime值设置为0的话,那么它将在执行开始的时间内抢占很多的CPU时间,直到自己的vruntime追赶上其他进程后才可能调度其他进程,这种情况显然是不公平的。所以CFS对每个CPU的执行队列都维护一个min_vruntime值,这个值纪录了这个CPU执行队列中vruntime的最小值,当队列中出现一个新建的进程时,它的初始化vruntime将不会被设置为0,而是根据min_vruntime的值为基础来设置。这样就保证了新建进程的vruntime与老进程的差距在一定范围内,不会因为vruntime设置为0而在进程开始的时候占用过多的CPU。
新建进程获得的实际vruntime值跟一些设置有关,比如:
[zorro@zorrozou-pc0 ~]$ cat/proc/sys/kernel/sched_child_runs_first
0
这个文件是fork之后是否让子进程优先于父进程执行的开关。0为关闭,1为打开。如果这个开关打开,就意味着子进程创建后,保证子进程在父进程之前被调度。另外,在源代码目录下的kernel/sched/features.h文件中,还规定了一系列调度器属性开关。而其中:
/*
* Place new tasks ahead so that they do not starve already running
* tasks
*/
SCHED_FEAT(START_DEBIT,true)
这个参数规定了新进程启动之后第一次运行会有延时。这意味着新进程的vruntime设置要比默认值大一些,这样做的目的是防止应用通过不停的fork来尽可能多的获得执行时间。子进程在创建的时候,vruntime的定义的步骤如下,首先vruntime被设置为min_vruntime。然后判断START_DEBIT位是否被值为true,如果是则会在min_vruntime的基础上增大一些,增大的时间实际上就是一个进程的调度延时时间,即上面描述过的calc_delta_fair()函数得到的结果。这个时间设置完毕之后,就检查sched_child_runs_first开关是否打开,如果打开(值被设置为1),就比较新进程的vruntime和父进程的vruntime哪个更小,并将新进程的vruntime设置为更小的那个值,而父进程的vruntime设置为更大的那个值,以此保证子进程一定在父进程之前被调度。
IO消耗型进程的处理