CFS中一些调度参数的实现原理
2016-07-27 09:05
489 查看
在cfs调度中有这么几个常用的参数:
sysctl_sched_latency:表示一段时间内,sched_entity肯定会被调度到一次,也就是一个sched_entity调度的最大的延时,2.6.35.13内核中默认是6ms。
sysctl_sched_min_granularity:表示调度的最小粒度,如果调度的时间间隔小于这个时间段,内核是不会挑选其他sched_entity进行调度,这个2.6.35.13内核中默认是2ms。
nr_latency:表示在上面的那段最大调度延迟中,最多处理的sched_entity。
上面的这些参数,书上都是这么说的,说下个人的理解。
对于sysctl_sched_latency参数是调度的最大延迟,在cfs中,调度根据sched_entity的vruntime来选择对应的调度单位,对于不同的sched_entity的load并不相同,cfs_rq中记录了running的sched_entity的总load,可以根据sched_entity的load占总cfs_rqload的比重来分配调度时间,内核中就是通过统计sched_entity的运行时间来确定是否运行的时间够了,如果够了,那么要重新选择vruntime最小的单位进行调度,这样才能保证在sysctl_sched_latency让所有的running的sched_entity运行一次。
sysctl_sched_min_granularity变量比较好理解,sched_tick中,应该判断当前运行的sched_entity的运行时间是否超过了sysctl_sched_min_granularity,如果未超过,就表示当前sched_entity还需要继续运行,如果超过了,可能会考虑让wakeup的sched_entity抢占调度。
nr_latency是延迟时间段能最大处理任务的数量,在这段时间点有可能切换进程,这几个时间点就是当一个sched_entity运行时间超过了sysctl_sched_min_granularity,那么就可以考虑切换进程了,这样如果每次都切换进程,那么延迟时间段内最大处理任务的数量就是nr_latency
nr_latency=sysctl_sched_latency/sysctl_sched_min_granularity
sched_entity的理论运行时间是按照cfs_rq中的总的load和当前sched_entity的load来平分sysctl_sched_latency的。
static u64 sched_slice(struct cfs_rq *cfs_rq, struct
sched_entity *se)
{
//获得总的运行时间段,这个时间有可能比sysctl_sched_latency要大
u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);
for_each_sched_entity(se) {
struct load_weight *load;
struct load_weight lw;
cfs_rq = cfs_rq_of(se);
load = &cfs_rq->load;//获得cfs_rq总的load
//如果se不再rq中,说明rq中的load不包括se的load,这个时候需要把se的load加到rq上
if (unlikely(!se->on_rq)) {
lw = cfs_rq->load;
update_load_add(&lw, se->load.weight);
load = &lw;
}
//根据slice是一轮调度总的时间,load是rq总的load,se->load.weight是se的load,
//这样就可以算出load占总load的百分比,然后体现在平分slice上
slice = calc_delta_mine(slice, se->load.weight, load);
}
return slice;
}
__sched_period函数是计算调度一轮所有的sched_entity所需要的时间
static u64 __sched_period(unsigned long nr_running)
{
u64 period = sysctl_sched_latency;
unsigned long nr_latency = sched_nr_latency;
if (unlikely(nr_running > nr_latency)) {
period = sysctl_sched_min_granularity;
period *= nr_running;
}
return period;
}
如果当前可以运行的sched_entity的个数超过了 nr_latency,
那么认为 在sysctl_sched_latency的一段时间内调度所有的sched_entity时间不够,
这时候需要按照nr_running的个树,扩大 sysctl_sched_latency。
时钟周期中断函数会调用scheduler_tick,最终调用了cfs中的check_preempt_tick,
检测当前调度的sched_entity是否需要被抢占,需要调度其他的sched_entity。
static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
unsigned long ideal_runtime, delta_exec;
ideal_runtime = sched_slice(cfs_rq, curr);
//获得当前进程的理想运行时间,按照sched_entity的load和rq的总load平分 sysctl_sched_latency所得的时间
delta_exec = curr->sum_exec_runtime – curr->prev_sum_exec_runtime;
//sum_exec_runtime是sched_entity自fork出来的运行时间,prev表示截止到上次切换进程的运行时间,
//所以这两个时间之差就是切换到当前进程后,该进程所运行的时间
if (delta_exec > ideal_runtime) {
//可以知道如果当前进程运行时间超过了理论运行时间,那么就应该让其它的进程运行了。
resched_task(rq_of(cfs_rq)->curr);
/*
* The current task ran long enough, ensure it doesn't get
* re-elected due to buddy favours.
*/
clear_buddies(cfs_rq, curr);
//如果curr被放在了cfs_rq的next或者last位,就应该清除这个引用。
return;
}
/*
* Ensure that a task that missed wakeup preemption by a
* narrow margin doesn't have to wait for a
full slice.
* This also mitigates buddy induced latencies under load.
*/
if (!sched_feat(WAKEUP_PREEMPT))
return;
if (delta_exec < sysctl_sched_min_granularity)
//如果进程运行的时间小于最小的进程切换的时间,那么是不需要切换进程的,切换的越频繁,成本开销也就越大
return;
if (cfs_rq->nr_running > 1) {
//给刚wakeup的进程一个机会,避免使他等太久的时间未得到调度(由于当前进程的理想运行时间过长),
//其实在wakeup一个进程 的时候已经有机会让wakeup的进程先调度,可能由于vruntime和curr的
//vruntime相距不大,没有得到运行的机会,这里再给新wakeup的进程一次被调度的机会。
struct sched_entity *se = __pick_next_entity(cfs_rq);
s64 delta = curr->vruntime - se->vruntime;
if (delta > ideal_runtime)
resched_task(rq_of(cfs_rq)->curr);
}
}
在切换sched_entity的时候会记录当前sched_entity的 prev_sum_exec_runtime,表示当前的 prev_sum_exec_runtime=sum_exec_runtime,至于最后pick出next
sched_entity后为什么要判断和当前vruntime之差大于理想运行时间,才能重新调度新的进程,猜测一下,对于一个刚刚wakeup的进程,sleep了足够长的时间了,一个sysctl_sched_latency就可以让curr进程的vruntime增加ideal_runtime(nice=0,优先级低的增加的会更快,优先级高的增加的会相对慢一些,需要多个sysctl_sched_latency的时间),那么可以认为curr的vruntime和wakeup的vruntime差距超过了ideal_runtime,curr相对于sleep进程已经执行了足够长的时间了,这时候即使curr在这一轮调度没有达到ideal_runtime的时间,那么也该需要新唤醒的进程轮到调度了,所以这个时候需要resched了,上面的分析只是个人一些猜测。望大牛多加指点。
sysctl_sched_latency:表示一段时间内,sched_entity肯定会被调度到一次,也就是一个sched_entity调度的最大的延时,2.6.35.13内核中默认是6ms。
sysctl_sched_min_granularity:表示调度的最小粒度,如果调度的时间间隔小于这个时间段,内核是不会挑选其他sched_entity进行调度,这个2.6.35.13内核中默认是2ms。
nr_latency:表示在上面的那段最大调度延迟中,最多处理的sched_entity。
上面的这些参数,书上都是这么说的,说下个人的理解。
对于sysctl_sched_latency参数是调度的最大延迟,在cfs中,调度根据sched_entity的vruntime来选择对应的调度单位,对于不同的sched_entity的load并不相同,cfs_rq中记录了running的sched_entity的总load,可以根据sched_entity的load占总cfs_rqload的比重来分配调度时间,内核中就是通过统计sched_entity的运行时间来确定是否运行的时间够了,如果够了,那么要重新选择vruntime最小的单位进行调度,这样才能保证在sysctl_sched_latency让所有的running的sched_entity运行一次。
sysctl_sched_min_granularity变量比较好理解,sched_tick中,应该判断当前运行的sched_entity的运行时间是否超过了sysctl_sched_min_granularity,如果未超过,就表示当前sched_entity还需要继续运行,如果超过了,可能会考虑让wakeup的sched_entity抢占调度。
nr_latency是延迟时间段能最大处理任务的数量,在这段时间点有可能切换进程,这几个时间点就是当一个sched_entity运行时间超过了sysctl_sched_min_granularity,那么就可以考虑切换进程了,这样如果每次都切换进程,那么延迟时间段内最大处理任务的数量就是nr_latency
nr_latency=sysctl_sched_latency/sysctl_sched_min_granularity
sched_entity的理论运行时间是按照cfs_rq中的总的load和当前sched_entity的load来平分sysctl_sched_latency的。
static u64 sched_slice(struct cfs_rq *cfs_rq, struct
sched_entity *se)
{
//获得总的运行时间段,这个时间有可能比sysctl_sched_latency要大
u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);
for_each_sched_entity(se) {
struct load_weight *load;
struct load_weight lw;
cfs_rq = cfs_rq_of(se);
load = &cfs_rq->load;//获得cfs_rq总的load
//如果se不再rq中,说明rq中的load不包括se的load,这个时候需要把se的load加到rq上
if (unlikely(!se->on_rq)) {
lw = cfs_rq->load;
update_load_add(&lw, se->load.weight);
load = &lw;
}
//根据slice是一轮调度总的时间,load是rq总的load,se->load.weight是se的load,
//这样就可以算出load占总load的百分比,然后体现在平分slice上
slice = calc_delta_mine(slice, se->load.weight, load);
}
return slice;
}
__sched_period函数是计算调度一轮所有的sched_entity所需要的时间
static u64 __sched_period(unsigned long nr_running)
{
u64 period = sysctl_sched_latency;
unsigned long nr_latency = sched_nr_latency;
if (unlikely(nr_running > nr_latency)) {
period = sysctl_sched_min_granularity;
period *= nr_running;
}
return period;
}
如果当前可以运行的sched_entity的个数超过了 nr_latency,
那么认为 在sysctl_sched_latency的一段时间内调度所有的sched_entity时间不够,
这时候需要按照nr_running的个树,扩大 sysctl_sched_latency。
时钟周期中断函数会调用scheduler_tick,最终调用了cfs中的check_preempt_tick,
检测当前调度的sched_entity是否需要被抢占,需要调度其他的sched_entity。
static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
unsigned long ideal_runtime, delta_exec;
ideal_runtime = sched_slice(cfs_rq, curr);
//获得当前进程的理想运行时间,按照sched_entity的load和rq的总load平分 sysctl_sched_latency所得的时间
delta_exec = curr->sum_exec_runtime – curr->prev_sum_exec_runtime;
//sum_exec_runtime是sched_entity自fork出来的运行时间,prev表示截止到上次切换进程的运行时间,
//所以这两个时间之差就是切换到当前进程后,该进程所运行的时间
if (delta_exec > ideal_runtime) {
//可以知道如果当前进程运行时间超过了理论运行时间,那么就应该让其它的进程运行了。
resched_task(rq_of(cfs_rq)->curr);
/*
* The current task ran long enough, ensure it doesn't get
* re-elected due to buddy favours.
*/
clear_buddies(cfs_rq, curr);
//如果curr被放在了cfs_rq的next或者last位,就应该清除这个引用。
return;
}
/*
* Ensure that a task that missed wakeup preemption by a
* narrow margin doesn't have to wait for a
full slice.
* This also mitigates buddy induced latencies under load.
*/
if (!sched_feat(WAKEUP_PREEMPT))
return;
if (delta_exec < sysctl_sched_min_granularity)
//如果进程运行的时间小于最小的进程切换的时间,那么是不需要切换进程的,切换的越频繁,成本开销也就越大
return;
if (cfs_rq->nr_running > 1) {
//给刚wakeup的进程一个机会,避免使他等太久的时间未得到调度(由于当前进程的理想运行时间过长),
//其实在wakeup一个进程 的时候已经有机会让wakeup的进程先调度,可能由于vruntime和curr的
//vruntime相距不大,没有得到运行的机会,这里再给新wakeup的进程一次被调度的机会。
struct sched_entity *se = __pick_next_entity(cfs_rq);
s64 delta = curr->vruntime - se->vruntime;
if (delta > ideal_runtime)
resched_task(rq_of(cfs_rq)->curr);
}
}
在切换sched_entity的时候会记录当前sched_entity的 prev_sum_exec_runtime,表示当前的 prev_sum_exec_runtime=sum_exec_runtime,至于最后pick出next
sched_entity后为什么要判断和当前vruntime之差大于理想运行时间,才能重新调度新的进程,猜测一下,对于一个刚刚wakeup的进程,sleep了足够长的时间了,一个sysctl_sched_latency就可以让curr进程的vruntime增加ideal_runtime(nice=0,优先级低的增加的会更快,优先级高的增加的会相对慢一些,需要多个sysctl_sched_latency的时间),那么可以认为curr的vruntime和wakeup的vruntime差距超过了ideal_runtime,curr相对于sleep进程已经执行了足够长的时间了,这时候即使curr在这一轮调度没有达到ideal_runtime的时间,那么也该需要新唤醒的进程轮到调度了,所以这个时候需要resched了,上面的分析只是个人一些猜测。望大牛多加指点。
相关文章推荐
- Linux socket 初步
- Linux Kernel 4.0 RC5 发布!
- linux lsof详解
- linux 文件权限
- Linux 执行数学运算
- 10 篇对初学者和专家都有用的 Linux 命令教程
- Linux 与 Windows 对UNICODE 的处理方式
- Ubuntu12.04下QQ完美走起啊!走起啊!有木有啊!
- 解決Linux下Android开发真机调试设备不被识别问题
- 运维入门
- 运维提升
- Linux 自检和 SystemTap
- Ubuntu Linux使用体验
- c语言实现hashmap(转载)
- Linux 信号signal处理机制
- linux下mysql添加用户
- Scientific Linux 5.5 图形安装教程