【WALT】WALT入口 update_task_ravg() 代码详解
@
【WALT】WALT入口 update_task_ravg()
代码版本:Linux4.9 android-msm-crosshatch-4.9-android12
代码展示
void update_task_ravg(struct task_struct *p, struct rq *rq, int event,
u64 wallclock, u64 irqtime) {
u64 old_window_start;
// ⑴ 判断是否进入 WALT 算法
if (!rq->window_start || sched_disable_window_stats ||
p->ravg.mark_start == wallclock)
return;
lockdep_assert_held(&rq->lock);
// ⑵ 获取 WALT 算法中上一个窗口的开始时间
old_window_start = update_window_start(rq, wallclock, event);
// ⑶ 如果任务刚初始化结束,不进入 WALT 算法,进入 `done`
if (!p->ravg.mark_start) {
update_task_cpu_cycles(p, cpu_of(rq), wallclock);
goto done;
}
// ⑷ 更新任务及 CPU 的 cycles
update_task_rq_cpu_cycles(p, rq, event, wallclock, irqtime);
// ⑸ 更新任务及 CPU 的 demand 及 pred_demand
update_task_demand(p, rq, event, wallclock);
// ⑹ 更新 CPU 的 busy time
update_cpu_busy_time(p, rq, event, wallclock, irqtime);
// ⑺ 更新任务的 pred_demand
update_task_pred_demand(rq, p, event);
// ⑻ 如果任务正在退出,进入 `done`
if (exiting_task(p))
goto done;
// 两个系统自带的 tracepoint
trace_sched_update_task_ravg(p, rq, event, wallclock, irqtime,
rq->cc.cycles, rq->cc.time, &rq->grp_time);
trace_sched_update_task_ravg_mini(p, rq, event, wallclock, irqtime,
rq->cc.cycles, rq->cc.time, &rq->grp_time);
done:
p->ravg.mark_start = wallclock;
run_walt_irq_work(old_window_start, rq);
}
代码逻辑
WALT 算法以任务为主,当任务被唤醒、任务开始执行、任务停止执行、任务退出、窗口滚动、频率变化、任务迁移、经过一个调度tick、在中断结束时会调用update_task_ravg()
。
其中,窗口是 WALT 算法中的一个特殊的设定,将在 update_task_demand()
与 update_cpu_busy_time()
中详细解释。
⑴ 判断是否进入 WALT 算法
在进入 WALT 算法后首先会判断当前任务所在的运行队列(runqueue)是否进行初始化,以及是否禁用 CPU 的窗口统计:if(!rq->window_start || sched_disable_window_stats...)
。如果没有初始化,就不会记录窗口的开始时间,任务负载就无法进行计算。有几点需要注意:
- 该处是指 rq,而非 cfs->rq 或 rt->rq,即该处不区分实时任务或普通任务;
- 任务/CPU 窗口(sched_ravg_window)是自定义的,不同版本代码或不同设备中设置的窗口大小是不一样的,调整的位置也不尽相同。
然后会判断窗口开始时间是否更新:if(...p->ravg.mark_start == wallclock)
。如果运行队列没有初始化,或禁用了 CPU 的窗口统计,或窗口开始时间没有更新,就会直接结束 WALT 算法。
⑵ 获取 WALT 算法中上一个窗口的开始时间
然后通过函数update_window_start()
获取上一个窗口的开始时间,存在变量old_window_start
中。
点击此处查看 update_window_start() 代码详解。
⑶ 如果任务刚初始化结束
如果任务刚初始化结束:if(!p->ravg.mark_start)
,还没有标记过任务的开始时间,就先通过函数 update_task_cpu_cycles()
更新一下该任务的 cycles 值(p->cpu_cycles
),然后进入 done
。
⑷ 更新任务及 CPU 的 cycles
和 update_task_cpu_cycles()
相似,但比其多更新了 CPU 的 cycles 值(rq->cc.cycles
)。
该函数更新的值会在 scale_exec_time() 中被用到。
⑸ 更新任务及 CPU 的 demand 及 pred_demand
在任务满足条件后,在不同情况下根据任务的开始时间、窗口的开始时间以及当前时间来计算任务在当前及之前M个窗口中的运行时间。在窗口结束时将运行时间进行归一化,并统计进任务的历史窗口中(sum_history[RAVG_HIST_SIZE]
)。
WALT 算法根据历史窗口中的值计算任务的 demand,根据桶算法计算任务的 pred_demand,并将 demand 与 pred_demand 统计进任务所在 CPU 的 rq(runqueue)中。
注意:以上说的 demand 与 pred_demand 都是预测值。
点击此处查看 update_task_demand() 代码详解。
⑹ 更新 CPU 的 busy time
在任务满足条件后,在不同情况下根据任务的开始时间、窗口的开始时间以及当前时间来计算任务在当前及上一个窗口中的运行时间,将不同窗口内的运行时间进行归一化,并根据任务的状态统计进任务的 curr_window
和 prev_window
中,以及任务所在 rq 的 curr_runnable_sum
和 prev_runnable_sum
中。
在窗口翻滚的时候更新任务的 window 值 以及 rq 的 runnable_sum 的值。
注意:以上说的 window 以及 runnable_sum 都是真实值。
点击此处查看 update_cpu_busy_time() 代码详解。
⑺ 更新任务的 pred_demand
如果符合条件的任务在当前窗口中预测出来的 pred_demand 值小于 curr_window
,则再次使用桶算法计算 pred_demand。
点击此处查看 update_task_pred_demand() 代码详解。
⑻ 如果任务正在退出
#define EXITING_TASK_MARKER 0xdeaddead
static inline int exiting_task(struct task_struct *p)
{
return (p->ravg.sum_history[0] == EXITING_TASK_MARKER);
}
当任务最近一个窗口的值为 0xdeaddead 时,意味着任务正在退出,进入 done
。
done
结束部分:
- 更新一下任务的开始时间:
p->ravg.mark_start = wallclock
。 - 执行
run_walt_irq_work()
,判断是否需要计算频率。具体可以查看 【WALT】频率计算。
函数调用时机
<kernel/sched/core.c>
try_to_wake_up()
try_to_wake_up_local()
scheduler_tick()
__schedule()
sched_exit()
<kernel/sched/walt.c>
sched_account_irqtime()
fixup_busy_time()
cpufreq_notifier_trans()
transfer_busy_time()
walt_irq_work()