详细代码
static inline u64 scale_exec_time(u64 delta, struct rq *rq)
{
u32 freq;
// ⑴ 将 CPU cycles 转换为 CPU 当前频率
freq = cpu_cycles_to_freq(rq->cc.cycles, rq->cc.time);
// ⑵ 归一化 delta
delta = DIV64_U64_ROUNDUP(delta * freq, max_possible_freq);
delta *= rq->cluster->exec_scale_factor;
delta >>= 10;
return delta;
}
函数核心作用
频率归一化:将执行时间(delta)转换为基于最大可能频率的等效值
跨集群标准化:考虑不同 CPU 集群(如 big.LITTLE)的性能差异
精度处理:保证计算结果的精确性和一致性
代码逻辑
scale_exec_time()函数用于给任务的运行时间delta进行归一化。
为什么要归一化
EAS主要针对异构CPU架构,如Arm big.LITTLE,因为这种架构有不同性能和功耗的CPU核心,不同CPU的最大算力、最大频率等都不同。假定一个任务在当前窗口中运行了5ms,对不同频率的两个CPU来说,5ms带来的负载是截然不同的。
WALT算法引入了一种类似权重的方法,根据CPU的频率(Frequency)和最大周期指令数(efficiency)来对任务的运行时间进行归一化。
1.将CPU cycles转换为CPU当前频率
freq = cpu_cycles_to_freq(rq->cc.cycles, rq->cc.time);
static inline u32 cpu_cycles_to_freq(u64 cycles, u64 period)
{
return div64_u64(cycles, period);
}
在这里freq = rq->cc.cycles / rq->cc.time。其中,rq->cc.cycles 和 rq->cc.time在函数update_task_rq_cpu_cycles()中更新:
static void
update_task_rq_cpu_cycles(struct task_struct *p, struct rq *rq, int event,
u64 wallclock, u64 irqtime)
{
u64 cur_cycles;
int cpu = cpu_of(rq);
lockdep_assert_held(&rq->lock); // 确保持有运行队列锁
if (!use_cycle_counter) { // 如果不使用周期计数器
rq->cc.cycles = cpu_cur_freq(cpu); // 直接使用当前频率
rq->cc.time = 1; // 时间设为1个单位
return;
}
cur_cycles = read_cycle_counter(cpu, wallclock);
/*
* If current task is idle task and irqtime == 0 CPU was
* indeed idle and probably its cycle counter was not
* increasing. We still need estimatied CPU frequency
* for IO wait time accounting. Use the previously
* calculated frequency in such a case.
*/
if (!is_idle_task(rq->curr) || irqtime) {
if (unlikely(cur_cycles < p->cpu_cycles)) // 处理计数器溢出
rq->cc.cycles = cur_cycles + (U64_MAX - p->cpu_cycles);
else
rq->cc.cycles = cur_cycles - p->cpu_cycles; // 正常情况计算增量
rq->cc.cycles = rq->cc.cycles * NSEC_PER_MSEC; // 转换为纳秒级精度
if (event == IRQ_UPDATE && is_idle_task(p))
/*
* Time between mark_start of idle task and IRQ handler
* entry time is CPU cycle counter stall period.
* Upon IRQ handler entry sched_account_irqstart()
* replenishes idle task's cpu cycle counter so
* rq->cc.cycles now represents increased cycles during
* IRQ handler rather than time between idle entry and
* IRQ exit. Thus use irqtime as time delta.
*/
rq->cc.time = irqtime; // 中断处理使用irqtime作为时间增量
else
rq->cc.time = wallclock - p->ravg.mark_start; // 正常情况使用时间差
BUG_ON((s64)rq->cc.time < 0); // 确保时间差非负
}
p->cpu_cycles = cur_cycles; // 保存当前周期计数供下次使用
trace_sched_get_task_cpu_cycles(cpu, event, rq->cc.cycles, rq->cc.time, p); // 记录tracepoint
}
函数核心作用
CPU 周期计数:精确测量任务执行期间消耗的 CPU 周期
频率估算:为后续的 CPU 频率相关计算提供基础数据
空闲检测:处理 CPU 空闲状态下的特殊计数情况
IRQ 处理:正确统计中断处理期间的 CPU 周期消耗
2.归一化delta
- delta = DIV64_U64_ROUNDUP(delta * freq, max_possible_freq);
即delta = delta * freq/max_possible_freq。
freq是当前CPU的频率:freq = rq->cc.cycles / rq->cc.time。
max_possible_freq 就是 max(policy->cpuinfo.max_freq)。
policy可以浅显地认为是簇号,如不同的policy指向小核簇、大核簇和超大核:
对于拥有多个CPU的簇来说,频率的计算在 scale_exec_time() 中进行,簇内每个CPU的频率都是一致的,因此一个簇会拥有一个当前频率和一个最大频率,即policy->cpuinfo.max_freq;
对于单个CPU来说,频率的计算在 scale_exec_time() 中进行,它也会有一个最大频率policy->cpuinfo.max_freq。
在运行该版本内核的pixel 3xl中,8个CPU分为小核簇与大核簇,他们的最大频率分别是381和1024。
- delta *= rq->cluster->exec_scale_factor;
cluster->exec_scale_factor = 1024 * cluster->efficiency/max_possible_efficiency
cluster->efficiency 指运行任务的 CPU 的每周期指令数 (instructions per cycle,IPC)。
max_possible_efficiency 指系统中任何 CPU 提供的最大 IPC。
这个值在设备树中给定,在运行该版本内核的 pixel 3xl 中,小核簇和大核簇的 max_possible_efficiency 分别是 1024 和 1740。
- delta >>= 10;
即delta = delta / 1024。
将三句代码一起看,能得出一个等式: