函数主要处理任务运行时间的累加和窗口更新,涉及任务在旧窗口和新窗口中的时间处理,以及任务状态的判断。通过对任务运行时间的精确跟踪,WALT算法能够更准确地评估系统负载。
【WALT】update_task_demand()代码详解
代码展示
static u64 update_task_demand(struct task_struct *p, struct rq *rq,
int event, u64 wallclock)
{
u64 mark_start = p->ravg.mark_start;
u64 delta, window_start = rq->window_start;
int new_window, nr_full_windows;
u32 window_size = sched_ravg_window;
u64 runtime;
// 用于判断是否进入新窗口的标志位
new_window = mark_start < window_start;
// ⑴ 不累加任务运行时间的条件判断
if (!account_busy_for_task_demand(rq, p, event)) {
if (new_window)
update_history(rq, p, p->ravg.sum, 1, event); // 窗口切换时保存历史
return 0; // 不统计的场景:空闲任务/特定事件类型
}
// ⑵ 仍在旧窗口中
if (!new_window) {
return add_to_task_demand(rq, p, wallclock - mark_start);
}
// ⑶ 进入新窗口
delta = window_start - mark_start;
nr_full_windows = div64_u64(delta, window_size); // 计算完整窗口数
window_start -= (u64)nr_full_windows * (u64)window_size;
runtime = add_to_task_demand(rq, p, window_start - mark_start);
update_history(rq, p, p->ravg.sum, 1, event); // 前导部分
if (nr_full_windows) {
u64 scaled_window = scale_exec_time(window_size, rq); // 按实际频率归一化
update_history(rq, p, scaled_window, nr_full_windows, event); // 完整窗口
runtime += nr_full_windows * scaled_window;
}
window_start += (u64)nr_full_windows * (u64)window_size;
mark_start = window_start;
runtime += add_to_task_demand(rq, p, wallclock - mark_start);
// ⑷ 返回值 runtime
return runtime;
}
核心作用
实时需求计算:动态统计任务在当前/历史窗口的 CPU 执行时间
窗口化负载跟踪:基于时间窗口(默认20ms)的离散化负载统计
跨窗口处理:正确处理任务执行跨越多个时间窗口的场景
预测基础:为后续的
pred_demand
计算提供历史数据
代码逻辑
1.更新标志位
WALT算法中,引入了一个新的概念:窗口(sched_ravg_window)
先介绍几个名词:
- ws:window_start,当前窗口的开始时间
- ms:mark_start,当前任务的开始时间
- wc:wallclock,进入WALT算法的时间
- nr_full_windows,如果进入新窗口,则代表就窗口到当前窗口所经历的完整的窗口个数
- delta:从任务开始到当前时间/新窗口开始时间所经历的时长
窗口分三种情况进行划分:
1.仍在旧窗口中
ws ms wc
| | |
V V V
|---------------|===============|
即进入 WALT 算法到时间还在 window_start 到 window_start + sched_ravg_window 之间
这种情况下,delta = wc - ms,只需要累加进任务时间,不需要更新
2.刚离开旧窗口,进入下一个窗口
ms ws wc
| | |
V V V
|---------------|===============|
即进入 WALT 算法到时间超过了 window_start + sched_ravg_window
但还没超过 window_start + sched_ravg_window * 2
这种情况下,delta 分为两块,一块是 ws - ms,一块是 wc - ws
两块都需要累加进任务时间,但 ws - ms 块需要进行更新,因为它在旧窗口中
3.经过了数个窗口后抵达新窗口
ms ws_tmp ws wc
| | | |
V V V V
|---------------|----------|...|----------|===============|
| |
|<--- nr_full_windows --->|
即进入 WALT 算法到时间超过了 window_start + sched_ravg_window * 2
其中经过了 nr_full_windows 个完整窗口
这种情况下,delta 分为三块,一块是 ws_tmp - ms,一块是 wc - ws,
一块是 sched_ravg_window * nr_full_windows
三块都需要累加进任务时间,但只有 wc - ws 块不需要进行更新,因为它在新窗口中
通过new_window = mark_start < window_start;来判断是否处于2、3种情况之中,如果new_window == 1,则处于2、3种情况之中,否则处于第一种情况。
2.不累加任务运行时间的条件判断
static int
account_busy_for_task_demand(struct rq *rq, struct task_struct *p, int event)
{
if (exiting_task(p) || is_idle_task(p))
return 0;
if (event == TASK_WAKE || (!SCHED_ACCOUNT_WAIT_TIME &&
(event == PICK_NEXT_TASK || event == TASK_MIGRATE)))
return 0;
if (event == TASK_UPDATE) {
if (rq->curr == p)
return 1;
return p->on_rq ? SCHED_ACCOUNT_WAIT_TIME : 0;
}
return 1;
}
函数的意义
这个函数的主要作用是帮助调度器确定何时应该将任务的状态变化计入调度统计。它根据任务的状态和触发的事件类型,决定是否将任务视为"busy"状态,这会影响:
CPU负载的计算
调度决策的统计信息
任务迁移和负载均衡的判断
通过这种方式,调度器可以更精确地跟踪系统负载和任务行为,从而做出更合理的调度决策。
在函数account_busy_for_task_demand()中会判断任务经过的时间是否是runnable或running时间,返回1则是,返回0则不是。
- 任务经过的时间是runnable或running,即返回1的情况,在当前版本内核中,SCHED_ACCOUNT_WALT_TIME默认为1。
任务更新且任务在就绪队列中,无论是不是当前任务
其他情况
- 任务经过的时间不是runnable 或 running,即返回0的情况
任务正在退出
任务是idle任务
任务刚被唤醒
任务更新且任务不在就绪队列中
如果任务经过的时间不是runnable或running时间,且正好进入新窗口,就不累加任务时间,直接通过update_history()将上一个窗口中已经累加的时间更新至任务结构体中(task_struct)。
3.仍在旧窗口中
根据开头的分析,这种情况下不需要通过update_history()更新时间,只需要通过add_to_task_demand()累加任务时间。
static u64 add_to_task_demand(struct rq *rq, struct task_struct *p, u64 delta)
{
// 1. 将 delta 时间进行归一化
delta = scale_exec_time(delta, rq);
// 2. 累加进 p->ravg.sum 中
p->ravg.sum += delta;
if (unlikely(p->ravg.sum > sched_ravg_window))
p->ravg.sum = sched_ravg_window;
return delta;
}
函数的意义
这个函数是Linux调度器(特别是WALT调度器)中任务需求统计的核心部分,它的作用包括:
归一化处理:考虑了CPU频率变化,使得不同频率下执行相同代码的任务具有可比的需求值
需求累计:维护任务在最近时间窗口内的CPU需求总量
限幅保护:防止单个任务的需求值过大影响调度决策
支持负载跟踪:为调度器的负载均衡和任务放置决策提供数据基础
这种机制使得调度器能够更准确地识别CPU密集型任务,并在多核系统中做出更合理的调度决策。
将归一化后的任务时间累加进p->ravg.sum中,在之后的update_history()中会将p->ravg.sum放进p->ravg.sum_history结构体中。其中,任务时间的归一化是WALT算法中的重要部分。scale_exec_time()代码详解
4.进入新窗口
新窗口分为两种情况,无论是那种情况,都需要累加ws_tmp - ms 和 wc - ws两部分。其中,如果刚离开旧窗口进入下一个窗口,则ws = ws_tmp。
处理ws_tmp - ms 部分:
- 先通过 delta = window_start - mark_start;计算总体经过的时间;
- 再通过 nr_full_windows = div64_u64(delta, window_size);计算经过的完整窗口的数量;
- 最后得到 ws_tmp:window_start -= (u64)nr_full_windows * (u64)window_size;
- 累加 ws_tmp - ms 部分时间:runtime = add_to_task_demand(rq, p, window_start - mark_start);
- 更新 ws_tmp - ms 部分时间:update_history(rq, p, p->ravg.sum, 1, event);
然后针对多个完整窗口情况进行时间更新。此处不需要通过add_to_task_demand()累加任务时间,因为任务在这些完整任务中的时间都是从窗口开始到窗口结束。
- 先对窗口时间进行归一化:scaled_window = scale_exec_time(window_size, rq);
- 更新时间:update_history(rq, p, scaled_window, nr_full_windows, event);
最后处理 wc - ws 部分:
- 把 ws 时间还原:window_start += (u64)nr_full_windows * (u64)window_size;
- mark_start = window_start;此处不是更新任务的开始时间,任务开始时间在WALT算法的done部分进行更新。如果任务开始时间在此处更新,会影响到update_cpu_busy_time()中的计算。
- 累加 wc - ws 部分时间:runtime += add_to_task_demand(rq, p, wallclock - mark_start);
5.返回值runtime
最后的返回值runtime在该版本内核中并未使用到,它是此次执行update_task_demand()时一共累加的任务runnable 和 running 时间,也就是上一次WALT算法开始到这一次WALT算法开始过程中,该任务的runnable 和 running 时间。