Skip to main content
 首页 » 操作系统

Linux 调度器之WALT负载计算

2022年07月19日234yxwkf

一、WALT简介

1. WALT(Windows-Assist Load Tracing),从字面意思来看,是以window作为辅助项来跟踪cpu load,用来表现cpu当前的loading情况,用于后续任务调度、迁移、负载均衡等功能。在 load 的基础上,添加对于demand的记录用于之后的预测。只统计runable和running time。

2. WALT由Qcom研发,主要用于移动设备对性能功耗要求比较高的场景,在与用户交互时需要尽快响应,要能及时反应负载的增加和减少以驱动频点及时的变化。当前的PELT负载跟踪算法更主要的是体现负载的连续性,对于突变性质的负载的反应不是很友好,负载上升慢,下降也慢。

3. 打开 CONFIG_SCHED_WALT 使能此feature。

4. 辅助计算项 window 的划分方法是将系统自启动开始以一定时间作为一个周期,分别统计不同周期内 Task 的 Loading 情况,并将其更新到Runqueue中;目前 Kernel 中是设置的一个 window 的大小是20ms,统计 5 个window内的Loading情况,当然,这也可以根据具体的项目需求进行配置。


二、相关数据结构

(1) 嵌入在 task_struct 中的 walt_task_struct

/* 
 * 'mark_start' 标记窗口内事件的开始(任务唤醒、任务开始执行、任务被抢占) 
 * 'sum' 表示任务在当前窗口内的可运行程度。它包含运行时间和等待时间,并按频率进行缩放。//就是在当前窗口的运行时间吧 
 * 'sum_history' 跟踪在之前的 RAVG_HIST_SIZE 窗口中看到的 'sum' 的历史记录。任务完全休眠的窗口将被忽略。 
 * 'demand' 表示在以前的 sysctl_sched_ravg_hist_size 窗口中看到的最大总和(根据window_policy选的)。 'demand'可以为任务驱动频率的改变。####### 
 * 'curr_window_cpu' 代表任务对当前窗口各CPU上cpu繁忙时间的贡献 
 * 'prev_window_cpu' 表示任务对前一个窗口中各个 CPU 上的 cpu 繁忙时间的贡献 
 * 'curr_window' 表示 curr_window_cpu 中所有条目的总和 
 * 'prev_window' 代表 prev_window_cpu 中所有条目的总和 
 * 'pred_demand' 代表任务当前预测的cpu繁忙时间 
 * 'busy_buckets' 将历史繁忙时间分组到用于预测的不同桶中 
 * 'demand_scaled' 表示任务的需求缩放到 1024 //就是上面demand成员缩放到1024 
 */ 
struct walt_task_struct { 
    u64        mark_start; 
    u32        sum, demand; //sum在 add_to_task_demand 中更新 
    u32        coloc_demand; //存的是5个历史窗口的平均值 
    u32        sum_history[RAVG_HIST_SIZE_MAX];  
    u32        *curr_window_cpu, *prev_window_cpu; //这个是per-cpu的 
    u32        curr_window, prev_window; 
    u32        pred_demand; 
    u8        busy_buckets[NUM_BUSY_BUCKETS]; //10个 
    u16        demand_scaled; 
    u16        pred_demand_scaled; 
    u64        active_time; //is_new_task中判断此值是小于100ms就认为是新任务,rollover_task_window是唯一更新位置 
    u32        unfilter; //update_history中对其进行赋值,colocate中选核时,是否需要跳过小核判断了它 
    u64        cpu_cycles; 
    ... 
}

(2) 嵌入在 rq 中的 walt_rq

struct walt_rq { 
    ... 
    struct walt_sched_stats walt_stats; 
    u64            window_start; 
    u32            prev_window_size; 
    u64            task_exec_scale; //walt_sched_init_rq中初始化为1024 
    u64            curr_runnable_sum; 
    u64            prev_runnable_sum; 
    u64            nt_curr_runnable_sum; 
    u64            nt_prev_runnable_sum; //nt 应该是walt认为的new task的意思 
    u64            cum_window_demand_scaled; 
    struct group_cpu_time    grp_time; 
    /* 
     * #define DECLARE_BITMAP_ARRAY(name, nr, bits) unsigned long name[nr][BITS_TO_LONGS(bits)] 
     * unsigned long top_tasks_bitmap[2][BITS_TO_LONGS(1000)]; //只跟踪curr和prev两个窗口的情况。 
     */ 
    DECLARE_BITMAP_ARRAY(top_tasks_bitmap, NUM_TRACKED_WINDOWS, NUM_LOAD_INDICES); 
    u8            *top_tasks[NUM_TRACKED_WINDOWS]; //2 指针数组 
    u8            curr_table; //只使用两个window进行跟踪,标识哪个是curr的,curr和prev构成一个环形数组,不停翻转 
    int            prev_top; //应该是rq->wrq.top_tasks[]中前一个窗最大值的下标 
    int            curr_top; //是rq->wrq.top_tasks[]中当前窗最大值的下标 
    u64            cycles; 
    ... 
}; 
 
struct walt_sched_stats { 
    int nr_big_tasks; 
    u64 cumulative_runnable_avg_scaled; //只统计runnable任务的,在update_window_start中赋值给rq->wrq.cum_window_demand_scaled 
    u64 pred_demands_sum_scaled; 
    unsigned int nr_rtg_high_prio_tasks; 
};

三、负载计算函数

1. walt算法负载计算入口函数

/* event 取 TASK_UPDATE 等,由于每个tick中断中都会调度,一般两次执行统计的 wc-ms 一般不会超过4ms */ 
void walt_update_task_ravg(struct task_struct *p, struct rq *rq, int event, u64 wallclock, u64 irqtime) //walt.c 
{ 
    u64 old_window_start; 
 
    /* 还没初始化或时间没更新,直接返回 */ 
    if (!rq->wrq.window_start || p->wts.mark_start == wallclock) 
        return; 
 
    lockdep_assert_held(&rq->lock); 
 
    /* 更新ws,返回最新的ws */ 
    old_window_start = update_window_start(rq, wallclock, event); 
 
    /* 对应还没初始化的情况, ws是per-rq的,ms是per-task的,wc是全局的 */ 
    if (!p->wts.mark_start) { 
        update_task_cpu_cycles(p, cpu_of(rq), wallclock); 
        goto done; 
    } 
    /*更新 rq->wrq.task_exec_scale 和 p->wts.cpu_cycles = cur_cycles; */ 
    update_task_rq_cpu_cycles(p, rq, event, wallclock, irqtime); 
 
    /*更新任务的负载和历史记录,返回 wc-ms 的差值,也就是距离上次统计任务运行的时间值 */ 
    update_task_demand(p, rq, event, wallclock); 
 
    /*更新任务和rq的window相关统计信息,记录per-rq的prev和curr两个窗口内任务负载分布情况 */ 
    update_cpu_busy_time(p, rq, event, wallclock, irqtime); 
 
    /*更新预测需求*/ 
    update_task_pred_demand(rq, p, event); 
 
    if (event == PUT_PREV_TASK && p->state) { 
        p->wts.iowaited = p->in_iowait; 
    } 
 
    trace_sched_update_task_ravg(p, rq, event, wallclock, irqtime, &rq->wrq.grp_time); 
 
    trace_sched_update_task_ravg_mini(p, rq, event, wallclock, irqtime, &rq->wrq.grp_time); 
 
done: 
    /* 更新per-task的 ms,ms是在动态变化的 */ 
    p->wts.mark_start = wallclock; 
 
    /*构成一个内核线程,每个窗口执行一次*/ 
    run_walt_irq_work(old_window_start, rq); 
}

此函数中的两个trace解析:

(1) trace_sched_update_task_ravg(p, rq, event, wallclock, irqtime, &rq->wrq.grp_time);

参数原型:

(struct task_struct *p, struct rq *rq, enum task_event evt, u64 wallclock, u64 irqtime, struct group_cpu_time *cpu_time)

打印内容:

<idle>-0     [004] d..2 50167.767150: sched_update_task_ravg: wc 50167994699141 ws 50167988000001 delta 6699140 
event PICK_NEXT_TASK cpu 4 cur_freq 434 cur_pid 0 task 17043 (kworker/u16:5) ms 50167994687631 delta 11510  
demand 3340045 coloc_demand: 1315008 sum 1235016 irqtime 0 pred_demand 3340045 rq_cs 1112353 rq_ps 4085339  
cur_window 930130 (0 136431 573963 0 219736 0 0 0 ) prev_window 2138941 (222138 156646 973556 0 219811 566790 0 0 )  
nt_cs 2513 nt_ps 20395 active_time 100000000 grp_cs 0 grp_ps 1691783, grp_nt_cs 0, grp_nt_ps: 0 curr_top 58 prev_top 133

字段解析:

wc:为参数4 wallclock; 
ws: 为 window_start,取自 rq->wrq.window_start;  
delta:取自 wallclock - rq->wrq.window_start 的差值。 
event:task_event_names[参数3], 字符串表示的事件类型 
cpu:取自 rq->cpu 
cur_freq:取自 rq->wrq.task_exec_scale,update_task_rq_cpu_cycles()中,若不使用 use_cycle_counter,赋值为 cpu_capaticy * (freq / maxfreq) 
cur_pid: 取自 rq->curr->pid 
task:取自参数1 p 的 p->pid 
kworker/u16:5:取自参数1 p 的 p->comm 
ms:是 mark_start 取自 p->wts.mark_start 
delta:打印中有两个同名的delta,这是第二个,取自 wallclock - p->wts.mark_start 
demand:取自 p->wts.demand,单位是ns,就是根据 p->wts.sum 取平均或和最近窗口两者之间的最大值 
coloc_demand:取自 p->wts.coloc_demand 
sum:取自 p->wts.sum,表示最近一个窗口运行时间之和,单位ns,在将其更新到history数组后,清0. 
irqtime:取自参数4 
pred_demand:取自 p->wts.pred_demand 
rq_cs:取自 rq->wrq.curr_runnable_sum 表示 
rq_ps:取自 rq->wrq.prev_runnable_sum 表示 
cur_window:取自 p->wts.curr_window,表示任务在当前窗口中所有cpu上的运行时间之和,是后面数组的累加。 
(0 136431 573963 0 219736 0 0 0 ):取自 p->wts.curr_window_cpu per-cpu的,表示任务在当前窗口中在每个cpu上运行的时间 
prev_window:取自 p->wts.prev_window 
(222138 156646 973556 0 219811 566790 0 0 ):取自 p->wts.prev_window_cpu 也是per-cpu的,表示任务在前一个窗口中在每个cpu上运行的时间 
nt_cs:取自 rq->wrq.nt_curr_runnable_sum nt应该表示的是new task的缩写 
nt_ps:取自 rq->wrq.nt_prev_runnable_sum 
active_time:取自 p->wts.active_time is_new_task()中判断它,唯一更新位置rollover_task_window()中调用is_new_task()判断是新任务时 p->wts.active_time += task_rq(p)->wrq.prev_window_size; 
grp_cs:取自 cpu_time ? cpu_time->curr_runnable_sum : 0 根据最后一个参数来判断是更新rq的还是更新rtg group的 
grp_ps:取自 cpu_time ? cpu_time->prev_runnable_sum : 0 
grp_nt_cs:取自 cpu_time ? cpu_time->nt_curr_runnable_sum : 0 
grp_nt_ps:取自 cpu_time ? cpu_time->nt_prev_runnable_sum : 0 
curr_top:取自 rq->wrq.curr_top 记录的是当前窗口中 rq->wrq.top_tasks[]中最大值的下标 
prev_top:取自 rq->wrq.prev_top 记录的是前一个窗口中 rq->wrq.top_tasks[]中最大值的下标

(2) trace_sched_update_task_ravg_mini(p, rq, event, wallclock, irqtime, &rq->wrq.grp_time);

参数原型:

(struct task_struct *p, struct rq *rq, enum task_event evt, u64 wallclock, u64 irqtime, struct group_cpu_time *cpu_time)

打印内容:

<idle>-0     [005] d..2 280546.887141: sched_update_task_ravg_mini: wc 112233604355205 ws 112233596000001 delta 8355204 
event PUT_PREV_TASK cpu 5 task 0 (swapper/5) ms 112233604337548 delta 17657 demand 2400000 rq_cs 1374618 rq_ps 1237818 
cur_window 0 prev_window 0 grp_cs 0 grp_ps 0

字段解析:

wc:取自参数 wallclock 
ws:取自 rq->wrq.window_start 
delta:取自 wallclock - rq->wrq.window_start 
event:取自 task_event_names[evt] 
cpu:取自 rq->cpu 
task:取自 p->pid 
swapper/5:取自 p->comm 
ms:取自 p->wts.mark_start 
delta:两个同名,这是第二个,取自 wallclock - p->wts.mark_start 
demand:取自 p->wts.demand 
rq_cs:取自 rq->wrq.curr_runnable_sum 
rq_ps:取自 rq->wrq.prev_runnable_sum 
cur_window:取自 p->wts.curr_window 
prev_window:取自 p->wts.prev_window 
grp_cs:取自 cpu_time ? cpu_time->curr_runnable_sum : 0 
grp_ps:取自 cpu_time ? cpu_time->prev_runnable_sum : 0

2. walt_update_task_ravg 的调用路径

    tick_setup_sched_timer //tick_sched.c timer到期回调函数中指定 tick_sched_timer 
        update_process_times //time.c tick中断中调用 
            scheduler_tick //core.c 周期定时器中断,传参(rq->curr, rq, TASK_UPDATE, wallclock, 0) 
        //任务显式阻塞或设置 TIF_NEED_RESCHED 并且在中断或返回用户空间调度点或preempt_enable() 
            __schedule //core.c 在这个主调度器函数中调用了三次,若选出的prev != next,调用两次,分别传参(prev, rq, PUT_PREV_TASK, wallclock, 0)和(next, rq, PICK_NEXT_TASK, wallclock, 0),若选出的prev == next,传参(prev, rq, TASK_UPDATE, wallclock, 0) 
__irq_enter //hardirq.h __handle_domain_irq()中调用,中断入口:handle_arch_irq=gic_handle_irq-->handle_domain_irq 
__do_softirq //softirq.c 
    account_irq_enter_time //vtime.h 
    account_irq_exit_time //vtime.h 
        irqtime_account_irq //cputime.c 若curr是idle task,并且是在硬中断或软中断上下文则调用,否则调用walt_sched_account_irqstart 
            walt_sched_account_irqend //walt.c,传参(curr, rq, IRQ_UPDATE, wallclock, delta); 
    move_queued_task 
    __migrate_swap_task 
    try_to_wake_up //core.c 当新选出的cpu和任务之前运行的不是同一个cpu调用 
    dl_task_offline_migration 
    push_dl_task 
    pull_dl_task 
    detach_task 
    push_rt_task 
    pull_rt_task 
        set_task_cpu //core.c 若新选出的cpu和任务之前的cpu不是同一个cpu,对任务进行迁移,然后调用,此时task->on_rq = TASK_ON_RQ_MIGRATING 
            fixup_busy_time //walt.c 连续调用三次,分别传参 (task_rq(p)->curr, task_rq(p), TASK_UPDATE, wallclock, 0)和(dest_rq->curr, dest_rq, TASK_UPDATE, wallclock, 0)和(p, task_rq(p), TASK_MIGRATE, wallclock, 0) 
cpufreq_freq_transition_end //cpufreq.c set_cpu_freq()中在设置频点前调用cpufreq_freq_transition_begin,设置后调用这个函数 
    cpufreq_notify_post_transition //cpufreq.c 相同参数调用两次         
        notifier_trans_block.notifier_call //回调,对应val=CPUFREQ_POSTCHANGE时通知 
            cpufreq_notifier_trans //walt.c 两层循环,对freq_domain_cpumask中的每一个cpu,对cluster中的每一个cpu,都调用,传参(rq->curr, rq, TASK_UPDATE, wallclock, 0) 
sync_cgroup_colocation //walt.c cpu_cgrp_subsys.attach=cpu_cgroup_attach-->walt_schedgp_attach中对每一个cpuset都调用 
sched_group_id_write //qc_vas.c 对应/proc/<pid>/sched_group_id 
    __sched_set_group_id //传参group_id=0才调用 
        remove_task_from_group //walt.c 传参(rq, p->wts.grp, p, REM_TASK) 
    __sched_set_group_id //传参group_id非0才调用 
        add_task_to_group //walt.c 传参(rq, grp, p, ADD_TASK) 
            transfer_busy_time //walt.c 连续调用两次,分别传参(rq->curr, rq, TASK_UPDATE, wallclock, 0)和(p, rq, TASK_UPDATE, wallclock, 0) 
    fixup_busy_time    //当task的cpu和参数cpu不是同一个时调用 
    walt_proc_user_hint_handler //walt.c /proc/sys/kernel/sched_user_hint作用load = load * (sched_user_hint / 100) 维持1s后清0 
        walt_migration_irq_work.func //walt.c irq_work 结构的回调 
walt_update_task_ravg //又回来了,work的响应函数中queue work,构成一个"内核线程不"停执行 
    run_walt_irq_work //walt.c 若新的window_start和旧的不是同一个就调用 
        walt_cpufreq_irq_work.func //walt.c irq_work 结构的回调 
            walt_irq_work //walt.c 对每个cluster的每个cpu都调用,传参(rq->curr, rq, TASK_UPDATE, wallclock, 0) 
    wake_up_q 
    wake_up_process 
    wake_up_state 
    default_wake_function 
        try_to_wake_up 
            walt_try_to_wake_up //walt.h 连续调用两次,分别传参(rq->curr, rq, TASK_UPDATE, wallclock, 0)和(p, rq, TASK_WAKE, wallclock, 0) 
                walt_update_task_ravg

walt_update_task_ravg 通过参数 event 可以控制哪些事件不更新负载

3. update_window_start 函数

/* 唯一调用路径:walt_update_task_ravg --> this */ 
static u64 update_window_start(struct rq *rq, u64 wallclock, int event) //walt.c 
{ 
    s64 delta; 
    int nr_windows; 
    u64 old_window_start = rq->wrq.window_start; 
 
    delta = wallclock - rq->wrq.window_start; 
    if (delta < 0) { 
        printk_deferred("WALT-BUG CPU%d; wallclock=%llu is lesser than window_start=%llu", rq->cpu, wallclock, rq->wrq.window_start); 
        SCHED_BUG_ON(1); 
    } 
 
    /* sched_ravg_window 默认是20ms, 不足一个窗口就不更新,直接退出*/ 
    if (delta < sched_ravg_window) 
        return old_window_start; 
 
    /* 下面是delta大于一个window的,计算历经的整窗的个数 */ 
    nr_windows = div64_u64(delta, sched_ravg_window); 
    rq->wrq.window_start += (u64)nr_windows * (u64)sched_ravg_window; /* 更新ws */ 
 
    rq->wrq.cum_window_demand_scaled = rq->wrq.walt_stats.cumulative_runnable_avg_scaled; 
    rq->wrq.prev_window_size = sched_ravg_window; 
 
    return old_window_start; 
}

可以看到,rq->wrq.window_start、rq->wrq.cum_window_demand_scaled 是最先更新的。然后返回旧的 window_start,

4. update_task_cpu_cycles 函数

static void update_task_cpu_cycles(struct task_struct *p, int cpu, u64 wallclock) //walt.c 
{ 
    if (use_cycle_counter) 
        p->wts.cpu_cycles = read_cycle_counter(cpu, wallclock); 
}

在 p->wts.mark_start 为0的时候,调用这个函数,应该是做初始化的。

5. update_task_rq_cpu_cycles 函数

/* 唯一调用路径 walt_update_task_ravg --> this */ 
static void update_task_rq_cpu_cycles(struct task_struct *p, struct rq *rq, int event, u64 wallclock, u64 irqtime) //walt.c 
{ 
    u64 cur_cycles; 
    u64 cycles_delta; 
    u64 time_delta; 
    int cpu = cpu_of(rq); 
 
    lockdep_assert_held(&rq->lock); 
 
    if (!use_cycle_counter) { 
        /* freq / maxfreq * cpu_capacity, arch_scale_cpu_capacity 为函数 topology_get_cpu_scale */ 
        rq->wrq.task_exec_scale = DIV64_U64_ROUNDUP(cpu_cur_freq(cpu) * arch_scale_cpu_capacity(cpu), rq->wrq.cluster->max_possible_freq); 
        return; 
    } 
 
    cur_cycles = read_cycle_counter(cpu, wallclock); /*return rq->wrq.cycles;*/ 
 
    /* 
     * 如果当前任务是空闲任务并且 irqtime == 0,CPU 确实空闲并且它的循环计数器可能没有增加。 
     * 我们仍然需要估计的 CPU 频率来计算 IO 等待时间。 在这种情况下使用先前计算的频率。 
     */ 
    if (!is_idle_task(rq->curr) || irqtime) { 
        if (unlikely(cur_cycles < p->wts.cpu_cycles)) //这应该是溢出了 
            cycles_delta = cur_cycles + (U64_MAX - p->wts.cpu_cycles); 
        else 
            cycles_delta = cur_cycles - p->wts.cpu_cycles; 
 
        cycles_delta = cycles_delta * NSEC_PER_MSEC; 
 
        if (event == IRQ_UPDATE && is_idle_task(p)) 
            /* 
             * 在空闲任务的 mark_start 和 IRQ 处理程序进入时间之间的时间是 CPU 周期计数器停止时间段。 
             * 在 IRQ 处理程序进入 walt_sched_account_irqstart() 时,补充空闲任务的 cpu 周期计数器,因 
             * 此cycles_delta 现在表示 IRQ 处理程序期间增加的周期,而不是从进入空闲到 IRQ 退出之间的时间段。 
             * 因此使用 irqtime 作为时间增量。 
             */ 
            time_delta = irqtime; 
        else 
            time_delta = wallclock - p->wts.mark_start; 
        SCHED_BUG_ON((s64)time_delta < 0); 
 
        /* (cycles_delta * cpu_capacity) / (time_delta * max_freq) = cycles_delta/time_delta * cpu_capacity/max_freq*/ 
        rq->wrq.task_exec_scale = DIV64_U64_ROUNDUP(cycles_delta * arch_scale_cpu_capacity(cpu), time_delta * rq->wrq.cluster->max_possible_freq); 
 
        trace_sched_get_task_cpu_cycles(cpu, event, cycles_delta, time_delta, p); 
    } 
 
    p->wts.cpu_cycles = cur_cycles; 
}

其中Trace: 

trace_sched_get_task_cpu_cycles(cpu, event, cycles_delta, time_delta, p);

参数原型:

(int cpu, int event, u64 cycles, u64 exec_time, struct task_struct *p)

打印内容:

shell svc 7920-7921  [006] d..4 53723.502493: sched_get_task_cpu_cycles: cpu=6 event=2 cycles=105682000000 exec_time=78229 freq=1350931 legacy_freq=2035200 
max_freq=2035200 task=19304 (kworker/u16:5)

字段解析:

前4个字段直接来自参数, 
freq:取自 cycles/exec_time, 其中 cycles 是乘以了 NSEC_PER_MSEC 的,exec_time 的单位是ns。 
legacy_freq:取自 cpu_rq(cpu)->wrq.cluster->max_possible_freq,单位KHz 
max_freq:取自 cpu_rq(cpu)->wrq.cluster->max_possible_freq * cpu_rq(cpu)->cpu_capacity_orig / SCHED_CAPACITY_SCALE 
task:取自 p->pid 
kworker/u16:5:取自 p->comm

6. update_history 解析

update_task_demand 中若判断不需要更新 task 的 p->wts.sum, 但是又有新窗口产生时,调用这个函数更新历史负载。

/* 
 * 当一个任务的新窗口开始时调用,记录最近结束的窗口的 CPU 使用率。 通常'samples'应该是1。 
 * 比如说,当一个实时任务同时运行而不抢占几个窗口时,它可以 > 1,也就是说连续运行3个窗口才 
 * 更新的话,samples就传3。 
 * 
 * update_task_demand()调用传参:(rq, p, p->wts.sum, 1, event)  sum 是几5个窗口的 
 */ 
static void update_history(struct rq *rq, struct task_struct *p, u32 runtime, int samples, int event) //walt.c 
{ 
    u32 *hist = &p->wts.sum_history[0]; 
    int ridx, widx; 
    u32 max = 0, avg, demand, pred_demand; 
    u64 sum = 0; 
    u16 demand_scaled, pred_demand_scaled; 
 
    /* Ignore windows where task had no activity */ 
    if (!runtime || is_idle_task(p) || !samples) 
        goto done; 
 
    /* Push new 'runtime' value onto stack */ 
    /* hist[5]中的元素向后移动samples个位置,runtime值插入到hist[0]中,hist[0]是最新的时间 */ 
    widx = sched_ravg_hist_size - 1; /* 5-1=4 */ 
    ridx = widx - samples; //widx=4, samples=1, ridx=3; samples=2, ridx=2 
    for (; ridx >= 0; --widx, --ridx) { 
        hist[widx] = hist[ridx]; 
        sum += hist[widx];  //此循环 sum = hist[4] + hist[3] + hist[2] + hist[1] 
        if (hist[widx] > max) 
            max = hist[widx]; //max保存最近4个窗中的最大值 
    } 
 
    /* 
     * 若samples=1, hist[0] = runtime 
     * 若samples=2, hist[0] = runtime, hist[1] = runtime 
     * ... 
     */ 
    for (widx = 0; widx < samples && widx < sched_ravg_hist_size; widx++) { 
        hist[widx] = runtime; //hist[0]中存放的是最近的一个窗中运行的时间 
        sum += hist[widx]; //sum再加上hist[0] 
        if (hist[widx] > max) 
            max = hist[widx]; //max保存的是最近5个窗中最大的值了 
    } 
 
    /* 将p->wts.sum放入history数组后就清0了, 也说明这个sum是一个窗的sum值 */ 
    p->wts.sum = 0; 
 
    /*可以通过 sched_window_stats_policy 文件进行配置下面4种window policy */ 
    if (sysctl_sched_window_stats_policy == WINDOW_STATS_RECENT) { //为0,返回最近一个窗口的运行时间值 
        demand = runtime; 
    } else if (sysctl_sched_window_stats_policy == WINDOW_STATS_MAX) { //为1,返回最近5个窗口运行时间的最大值 
        demand = max; 
    } else { 
        avg = div64_u64(sum, sched_ravg_hist_size); //求最近5个窗口运行时间的平均值 
        if (sysctl_sched_window_stats_policy == WINDOW_STATS_AVG) //为3,返回最近5个窗口平均运行时间值 
            demand = avg; 
        else 
            demand = max(avg, runtime); //为2,默认配置,返回最近5个窗口平均运行时间值 与 最近1个窗口运行时间值中的较大的那个 
    } 
 
    pred_demand = predict_and_update_buckets(p, runtime); 
 
    /* demand_scaled = demand/(window_size/1024) == (demand / window_size) * 1024 
     * 传参demand可以认为是p的负载了 
     */ 
    demand_scaled = scale_demand(demand); 
    /* pred_demand_scaled = pred_demand/(window_size/1024) == (pred_demand / window_size) * 1024 */ 
    pred_demand_scaled = scale_demand(pred_demand); 
 
    /* 
     * 限流的deadline调度类任务出队列时不改变p->on_rq。 由于出队递减 walt stats 避免再次递减它。 
     * 当窗口滚动时,累积窗口需求被重置为累积可运行平均值(来自运行队列上的任务的贡献)。如果当前任务已经出队, 
     * 则它的需求不包括在累积可运行平均值中。所以将任务需求单独添加到累积窗口需求中。 
     */ 
    /*这里增加的是rq上的统计值,不是per-entity的了*/ 
    if (!task_has_dl_policy(p) || !p->dl.dl_throttled) { 
        if (task_on_rq_queued(p)) { 
            fixup_walt_sched_stats_common(rq, p, demand_scaled, pred_demand_scaled); /*这里加的是demand_scaled的差值*/ 
        } else if (rq->curr == p) { 
            walt_fixup_cum_window_demand(rq, demand_scaled); 
        } 
    } 
 
    /*赋值给per-entiry上的统计值,demand_scaled 对 p->wts.demand_scaled 的赋值一定要保证,这是walt负载跟踪算法重要的部分*/ 
    p->wts.demand = demand; /* 对应一个窗中运行的时间(根据window policy不同而有差异) */ 
    p->wts.demand_scaled = demand_scaled; /* 对应一个窗中运行的时间(根据window policy不同而有差异)缩放到1024 */ ############# 
    p->wts.coloc_demand = div64_u64(sum, sched_ravg_hist_size); /*5个窗口运行时间之和除以5,即5个窗口的平均运行时间*/ 
    p->wts.pred_demand = pred_demand; 
    p->wts.pred_demand_scaled = pred_demand_scaled; 
 
    /* demand_scaled 大于指定的阈值时,会做一些事情 */ 
    if (demand_scaled > sysctl_sched_min_task_util_for_colocation) { 
        p->wts.unfilter = sysctl_sched_task_unfilter_period; /*单位是ns,默认值是100ms*/ 
    } else { 
        if (p->wts.unfilter) 
            p->wts.unfilter = max_t(int, 0, p->wts.unfilter - rq->wrq.prev_window_size); //相当于衰减一个窗口的大小 
    } 
 
done: 
    trace_sched_update_history(rq, p, runtime, samples, event); 
}

其中Trace:

trace_sched_update_history(rq, p, runtime, samples, event);

参数原型:

(struct rq *rq, struct task_struct *p, u32 runtime, int samples, enum task_event evt)

打印内容:

sched_update_history: 24647 (kworker/u16:15): runtime 279389 samples 1 event TASK_WAKE demand 717323 
coloc_demand 717323 pred_demand 279389 (hist: 279389 88058 520130 1182596 1516443) cpu 1 nr_big 0

字段解析:

24647:取自 p->pid 
kworker/u16:15:取自 p->comm 
runtime:来自参数3,表示最近一个窗口中的运行时间,也是 p->wts.sum 的值 
samples:来自参数4,表示更新几个窗的历史 
event:取自 task_event_names[event] 
demand:取自 p->wts.demand,是scale之前的根据不同window policy计算出来的负载值 
coloc_demand:取自 p->wts.coloc_demand,即5个窗口的平均值 
pred_demand:取自 p->wts.pred_demand,表示预测的负载需求 
(hist: 279389 88058 520130 1182596 1516443):取自 p->wts.sum_history[5],是任务在最近5个窗口中分别运行的时间 
cpu:取自 rq->cpu 
nr_big:取自 rq->wrq.walt_stats.nr_big_tasks

用于预测任务的 demand 的 bucket 相关更新:

static inline u32 predict_and_update_buckets(struct task_struct *p, u32 runtime) //walt.c 
{ 
 
    int bidx; 
    u32 pred_demand; 
 
    if (!sched_predl) //为1 
        return 0; 
 
    /* 根据传入的时间值获得一个桶的下标,桶一共有10个成员 */ 
    bidx = busy_to_bucket(runtime); 
    /* 使用 p->wts.busy_buckets 用于计算 */ 
    pred_demand = get_pred_busy(p, bidx, runtime); 
    /* 更新 p->wts.busy_buckets */ 
    bucket_increase(p->wts.busy_buckets, bidx); 
 
    return pred_demand; 
} 
 
static inline int busy_to_bucket(u32 normalized_rt) 
{ 
    int bidx; 
 
    bidx = mult_frac(normalized_rt, NUM_BUSY_BUCKETS, max_task_load()); /*args1*10/16; arg1*arg2/arg3*/ 
    bidx = min(bidx, NUM_BUSY_BUCKETS - 1); //min(p->wts.sum * 10 / 16, 9) 运行一个满窗是桶10,运行1ms-2ms返回1 
 
    /* 合并最低的两个桶。 最低频率落入第二桶,因此继续预测最低桶是没有用的。*/ 
    if (!bidx) 
        bidx++; 
 
    return bidx; 
} 
 
/* 
 * get_pred_busy - 计算运行队列上的任务的预测需求 
 * 
 * @p:正在更新预测的任务 
 * @start: 起始桶。 返回的预测不应低于此桶。 
 * @runtime:任务的运行时间。 返回的预测不应低于此运行时。 
 * 注意:@start 可以从@runtime 派生。 传入它只是为了在某些情况下避免重复计算。 
 * 
 * 根据传入的@runtime 为任务@p 返回一个新的预测繁忙时间。该函数搜索表示繁忙时间等于或大于@runtime 
 * 的桶,并尝试找到用于预测的桶。 一旦找到,它会搜索历史繁忙时间并返回落入桶中的最新时间。 如果不 
 * 存在这样的繁忙时间,则返回该桶的中间值。 
 */ 
/*假设传参是p->wts.sum=8ms,那么传参就是(p, 5, 8),*/ 
static u32 get_pred_busy(struct task_struct *p, int start, u32 runtime) 
{ 
    int i; 
    u8 *buckets = p->wts.busy_buckets; //10个元素 
    u32 *hist = p->wts.sum_history; //5个元素 
    u32 dmin, dmax; 
    u64 cur_freq_runtime = 0; 
    int first = NUM_BUSY_BUCKETS, final; //从最大值10开始找 
    u32 ret = runtime; 
 
    /* skip prediction for new tasks due to lack of history */ 
    /* 由于累积运行时间小于100ms的新任务缺少历史运行时间,不对其进行预测 */ 
    if (unlikely(is_new_task(p))) 
        goto out; 
 
    /* find minimal bucket index to pick */ 
    /* 找到最小的桶下标进行pick, 只要桶中有数据就选择 */ 
    for (i = start; i < NUM_BUSY_BUCKETS; i++) { 
        if (buckets[i]) { 
            first = i; 
            break; 
        } 
    } 
 
    /* 若没找到桶下标,就直接返回 runtime,注意 runtime 可能大于10 */ 
    if (first >= NUM_BUSY_BUCKETS) 
        goto out; 
 
    /* 计算用于预测的桶 */ 
    final = first; 
 
    /* 确定预测桶的需求范围 */ 
    if (final < 2) { 
        /* 最低的两个桶合并 */ 
        dmin = 0; 
        final = 1; 
    } else { 
        dmin = mult_frac(final, max_task_load(), NUM_BUSY_BUCKETS); //final * 20 / 10, max_task_load返回一个满窗 
    } 
    dmax = mult_frac(final + 1, max_task_load(), NUM_BUSY_BUCKETS); //(final + 1) * 20 / 10 
 
    /* 
     * search through runtime history and return first runtime that falls 
     * into the range of predicted bucket. 
     * 搜索运行历史并返回落在预测桶范围内的第一个运行。在最近的5个窗口中查找 
     */ 
    for (i = 0; i < sched_ravg_hist_size; i++) { 
        if (hist[i] >= dmin && hist[i] < dmax) { 
            ret = hist[i]; 
            break; 
        } 
    } 
    /* no historical runtime within bucket found, use average of the bin  
     * 若找不到存储桶内的历史运行时间,就使用垃圾桶的平均值 */ 
    if (ret < dmin) 
        ret = (dmin + dmax) / 2; 
    /* 
     * 在窗口中间更新时,运行时间可能高于所有记录的历史记录。 始终至少预测运行时间。 
     */ 
    ret = max(runtime, ret); 
out: 
    /* 由于 cur_freq_runtime 是0,所以 pct 恒为0 */ 
    trace_sched_update_pred_demand(p, runtime, mult_frac((unsigned int)cur_freq_runtime, 100,  sched_ravg_window), ret); 
 
    return ret; 
} 
 
/* 
 * bucket_increase - 更新所有桶的计数 
 * 
 * @buckets:跟踪任务繁忙时间的桶数组 
 * @idx: 要被递增的桶的索引 
 * 
 * 每次完成一个完整的窗口时,运行时间落入 (@idx) 的桶计数增加。 所有其他桶的计数都会衰减。  
 * 根据桶中的当前计数,增加和衰减的速率可能不同。 
 */ 
/*传参: (p->wts.busy_buckets, bidx)*/ 
static inline void bucket_increase(u8 *buckets, int idx) 
{ 
    int i, step; 
 
    for (i = 0; i < NUM_BUSY_BUCKETS; i++) { //10 
        if (idx != i) { //不相等就衰减 
            if (buckets[i] > DEC_STEP) //2 
                buckets[i] -= DEC_STEP; //2 
            else 
                buckets[i] = 0; 
        } else { //相等 
            step = buckets[i] >= CONSISTENT_THRES ? INC_STEP_BIG : INC_STEP; //16 16 8 
            if (buckets[i] > U8_MAX - step) //255-step 
                buckets[i] = U8_MAX; //255 
            else 
                buckets[i] += step; //就是加step,上面判断是为了不要溢出 
        } 
    } 
}

其中Trace:

trace_sched_update_pred_demand(p, runtime, mult_frac((unsigned int)cur_freq_runtime, 100,  sched_ravg_window), ret);

参数原型:

(struct task_struct *p, u32 runtime, int pct, unsigned int pred_demand)

打印内容:

sched_update_pred_demand: 1174 (Binder:1061_2): runtime 556361 pct 0 cpu 1 pred_demand 556361 (buckets: 0 255 0 0 0 0 0 0 0 0) 

字段解析:

1174:取自 p->pid 
Binder:1061_2:取自 p->comm 
runtime:取自参数2 
pct:取自参数3 
cpu:取自task_cpu(p) 
pred_demand:取自参数4 
(buckets: 0 255 0 0 0 0 0 0 0 0):取自 p->wts.busy_buckets[10]
/*  
 * update_history --> this,如果task在rq上才会调用传参: (rq, p, demand_scaled, pred_demand_scaled),参数是缩放到0--1024后的 
 * 也就是说这个函数里面计算的包含 runnable 的 
 */ 
static void fixup_walt_sched_stats_common(struct rq *rq, struct task_struct *p, u16 updated_demand_scaled, u16 updated_pred_demand_scaled) 
{ 
    /* p->wts.demand_scaled 约是由 p->wts.sum scale后得来的(window plicy策略影响), 后者是一个窗口中任务运行的时长。新的减旧的,结果处于[-1024,1024] */ 
    s64 task_load_delta = (s64)updated_demand_scaled - p->wts.demand_scaled; 
    /* p->wts.pred_demand_scaled 是由桶算法预测得来的 */ 
    s64 pred_demand_delta = (s64)updated_pred_demand_scaled - p->wts.pred_demand_scaled; 
 
    /* 直接加上传入的增量,注意增量可能是负数,一个进程的负载变低了,差值就是负数了*/ 
    fixup_cumulative_runnable_avg(&rq->wrq.walt_stats, task_load_delta, pred_demand_delta); 
    /*累加demand_scaled的增量*/ 
    walt_fixup_cum_window_demand(rq, task_load_delta); /*上下两个函数都是对rq->wrq.中的成员赋值*/ 
} 
 
/* 
 * 如果task在rq上调用路径:update_history --> fixup_cumulative_runnable_avg 传参:(&rq->wrq.walt_stats, task_load_delta, pred_demand_delta) 
 * 传参为时间差值。 
 */ 
static inline void fixup_cumulative_runnable_avg(struct walt_sched_stats *stats, s64 demand_scaled_delta, s64 pred_demand_scaled_delta) 
{ 
    /* 
     * 增量差值可正可负,rq 的 cumulative_runnable_avg_scaled 初始化后就只有在这里有赋值了。 
     * 这里根据根据当前窗口负载值快速变化。 
     */ 
    stats->cumulative_runnable_avg_scaled += demand_scaled_delta; 
    BUG_ON((s64)stats->cumulative_runnable_avg_scaled < 0); 
 
    stats->pred_demands_sum_scaled += pred_demand_scaled_delta; 
    BUG_ON((s64)stats->pred_demands_sum_scaled < 0); 
}

说明 rq->wrq.walt_stats.cumulative_runnable_avg_scaled 和 rq->wrq.walt_stats.pred_demands_sum_scaled 统计的只是 runnable 状态的负载值。这里加上有符号的delta值,可以快速的反应runnable状态的负载的变化。

/*  
 * 如果task在rq上调用路径:update_history --> fixup_walt_sched_stats_common --> this 传参:(rq, task_load_delta) 
 * 如果rq->curr == p 时调用路径:update_history --> this 传参:(rq, demand_scaled) 
 * 说明这里面更新的成员统计的级包括 runnable 的分量,也保留 running 的分量 
 */ 
static inline void walt_fixup_cum_window_demand(struct rq *rq, s64 scaled_delta) 
{ 
    rq->wrq.cum_window_demand_scaled += scaled_delta; 
 
    if (unlikely((s64)rq->wrq.cum_window_demand_scaled < 0)) 
        rq->wrq.cum_window_demand_scaled = 0; 
}

rq->wrq.cum_window_demand_scaled 统计的既包括 runnable 的又包括 running 的。runnable 的累加的是差值,而 running 的累加的直接是 demand_scaled 的值,若是一部分 runnable 的任务变成 running 了,前者减少,后者增加,体现在结果上可能是不变的。

7. update_task_demand 函数

/* 
 * 计算任务的cpu需求和/或更新任务的cpu需求历史 
 * 
 * ms = p->wts.mark_start 
 * wc = wallclock 
 * ws = rq->wrq.window_start 
 * 
 * 三种可能: 
 *  a) 任务事件包含在一个窗口中。 16ms per-window, window_start < mark_start < wallclock 
 *       ws    ms    wc 
 *       |    |    | 
 *       V    V    V 
 *       |---------------| 
 * 
 * 在这种情况下,如果事件是合适的 p->wts.sum 被更新(例如:event == PUT_PREV_TASK) 
 * 
 * b) 任务事件跨越两个窗口。mark_start < window_start < wallclock 
 * 
 *       ms    ws     wc 
 *       |    |     | 
 *       V    V     V 
 *      ------|------------------- 
 * 
 * 在这种情况下,如果事件是合适的 p->wts.sum 更新为 (ws - ms) ,然后记录一个新的窗口的采样,如果事件是合 
 * 适的然后将 p->wts.sum 设置为 (wc - ws) 。 
 * 
 * c) 任务事件跨越两个以上的窗口。 
 * 
 *        ms ws_tmp                   ws  wc 
 *        |  |                       |   | 
 *        V  V                       V   V 
 *        ---|-------|-------|-------|-------|------ 
 *           |                   | 
 *           |<------ nr_full_windows ------>| 
 * 
 * 在这种情况下,如果事件是合适的,首先 p->wts.sum 更新为 (ws_tmp - ms) ,p->wts.sum 被记录,然后,如果 
 * event 是合适的 window_size 的 'nr_full_window' 样本也被记录,最后如果 event 是合适的,p->wts.sum 更新 
 * 到 (wc - ws)。 
 * 
 * 重要提示:保持 p->wts.mark_start 不变,因为 update_cpu_busy_time() 依赖它! 
 * 
 */ 
/* walt_update_task_ravg-->this 唯一调用位置 */ 
static u64 update_task_demand(struct task_struct *p, struct rq *rq, int event, u64 wallclock) //walt.c 
{ 
    u64 mark_start = p->wts.mark_start; //进来时还没更新 
    u64 delta, window_start = rq->wrq.window_start; //进来时已经更新了 
    int new_window, nr_full_windows; 
    u32 window_size = sched_ravg_window; //20ms 
    u64 runtime; 
 
    new_window = mark_start < window_start; //若为真说明经历了新窗口 
    /* 若判断不需要更新负载,直接更新历史 p->wts.sum_history[],而没有更新 p->wts.sum */ 
    if (!account_busy_for_task_demand(rq, p, event)) { 
        if (new_window) { 
            /* 
             * 如果计入的时间没有计入繁忙时间,并且新的窗口开始, 
             * 则只需要关闭前一个窗口与预先存在的需求。 多个窗口 
             * 可能已经过去,但由于空窗口被丢弃,因此没有必要考虑这些。 
             * 
             * 如果被累积的时间没有被计入繁忙时间,并且有新的窗口开始, 
             * 则只需要与预先存在需求的前一个窗口被关闭。 虽然可能有多 
             * 个窗口已经流逝了,但由于WALT算法是空窗口会被丢弃掉,因 
             * 此没有必要考虑这些。 
             */ 
            update_history(rq, p, p->wts.sum, 1, event); 
        } 
        return 0; 
    } 
    /* 下面是需要更新的情况了 */ 
 
    /* (1) 还是同一个窗口,对应上面的情况a */ 
    if (!new_window) { 
        /* 简单的情况 - 包含在现有窗口中的繁忙时间。*/ 
        return add_to_task_demand(rq, p, wallclock - mark_start); 
    } 
 
    /* (2) 下面就是跨越了窗口,先求情况b */ 
    /* 繁忙时间至少跨越两个窗口。 暂时将 window_start 倒回到 mark_start 之后的第一个窗口边界。*/ 
    delta = window_start - mark_start; 
    nr_full_windows = div64_u64(delta, window_size); 
    window_start -= (u64)nr_full_windows * (u64)window_size; 
 
    /* Process (window_start - mark_start) first */ 
    /* 这里累加的是  情况b/情况c 中ws_tmp-ms这段的delta值 */ 
    runtime = add_to_task_demand(rq, p, window_start - mark_start); 
 
    /* Push new sample(s) into task's demand history */ 
    /* 将最开始的不足一个window窗口大小的delta计算出来的p->wts.sum放入历史数组中 */ 
    update_history(rq, p, p->wts.sum, 1, event); 
 
    /* (3) 下面就对应情况c了,由于c和b都有最开始不足一个窗口的一段,在上面计算b时一并计算了 */ 
    if (nr_full_windows) { 
        u64 scaled_window = scale_exec_time(window_size, rq); //等于直接return window_size 
 
        /* 一下子更新 nr_full_windows 个窗口的负载到历史窗口负载中,每个窗口都是满窗 */ 
        update_history(rq, p, scaled_window, nr_full_windows, event); 
        /* runtime 累积运行时间进行累加 ==>只要搞清什么时候标记ms和什么时候调用这个函数计算负载,就可以知道计算的是哪段的 ######## */ 
        runtime += nr_full_windows * scaled_window; 
    } 
 
    /* 将 window_start 滚回当前以处理当前窗口,以便于计算当前窗口中的剩余部分。*/ 
    window_start += (u64)nr_full_windows * (u64)window_size; 
 
    /* 这里是计算情况b和情况c的wc-ws段 */ 
    mark_start = window_start; 
 
    runtime += add_to_task_demand(rq, p, wallclock - mark_start); //runtime 继续累加 
 
    /* 返回值表示此次 update_task_demand 更新的时间值,是 wc-ms 的差值 */ 
    return runtime; 
}

此函数中始终没有更新回去 p->wts.mark_start,其是在 walt_update_task_ravg 函数最后更新的。rq->wrq.window_start 在上面第一个函数中就更新了。

/* update_task_demand --> this */ 
static int account_busy_for_task_demand(struct rq *rq, struct task_struct *p, int event) //walt.c 
{ 
    /* (1) 不需要统计 idle task 的 demand,直接返回*/ 
    if (is_idle_task(p)) 
        return 0; 
 
    /* 
     * 当一个任务被唤醒时,它正在完成一段非繁忙时间。 同样,如果等待时间 
     * 不被视为繁忙时间,那么当任务开始运行或迁移时,它并未运行并且正在完成 
     * 一段非繁忙时间。 
     */ 
    /*就是这些情况跳过统计,!SCHED_ACCOUNT_WAIT_TIME 恒为假,所以是只判断了 TASK_WAKE */ 
    /* (2) 是唤醒事件 或 不需要计算walit事件并且事件是pick和migrate, 不需要更新 */ 
    if (event == TASK_WAKE || (!SCHED_ACCOUNT_WAIT_TIME && (event == PICK_NEXT_TASK || event == TASK_MIGRATE))) 
        return 0; 
 
    /* (3) idle进程退出的时候也不需要统计 */ 
    if (event == PICK_NEXT_TASK && rq->curr == rq->idle) 
        return 0; 
 
    /* 
     * TASK_UPDATE can be called on sleeping task, when its moved between related groups 
     */ 
    /*context_switch()的时候更改的rq->curr*/ 
    /* (4) 若是update事件,且p是curr任务,需要更新。否则若p在队列上需要更新,不在队列上不需要更新 */ 
    if (event == TASK_UPDATE) { 
        if (rq->curr == p) 
            return 1; 
 
        return p->on_rq ? SCHED_ACCOUNT_WAIT_TIME : 0; //这里可调整是否记录任务在rq上的等待的时间 
    } 
 
    /* (5) 都不满足,默认是需要更新 */ 
    return 1; 
}

p是idle task,或 事件是 TASK_WAKE,或idle任务退出时的 PICK_NEXT_TASK 事件,或事件是 TASK_UPDATE 但是 p 不是curr任务也没有在rq上,就不需要计算busy time。只有事件是 TASK_UPDATE,且任务p是 rq->curr 任务或者 p是在rq 上等待,则需要更新。若不需要更新的话,又产生了新的窗口,那就调用 update_history()更新负载历史就退出了。

/* update_task_demand --> this 唯一调用路径也是在 walt_update_task_ravg 中 */ 
static u64 add_to_task_demand(struct rq *rq, struct task_struct *p, u64 delta) //walt.c 
{ 
    /* delta = (delta * rq->wrq.task_exec_scale) >> 10, 由于 rq->wrq.task_exec_scale 初始化为1024,所以还是delta*/ 
    delta = scale_exec_time(delta, rq); 
    /* 这里更新了 p->wts.sum,并将最大值钳位在一个窗口大小*/ 
    p->wts.sum += delta; 
    if (unlikely(p->wts.sum > sched_ravg_window)) 
        p->wts.sum = sched_ravg_window; 
 
    return delta; 
}

更新 p->wts.sum 值,并且返回 delta 值。这也是 sum 的唯一更新位置,唯一调用路径也是从 walt_update_task_ravg 函数调用下来的。

8. update_cpu_busy_time 函数

/* walt_update_task_ravg --> this 这是唯一调用路径,传参(p, rq, event, wallclock, irqtime)*/ 
static void update_cpu_busy_time(struct task_struct *p, struct rq *rq, int event, u64 wallclock, u64 irqtime) 
{ 
    int new_window, full_window = 0; 
    int p_is_curr_task = (p == rq->curr); 
    u64 mark_start = p->wts.mark_start; 
    u64 window_start = rq->wrq.window_start; //walt_update_task_ravg-->update_window_start 最先更新的rq->wrq.window_start 
    u32 window_size = rq->wrq.prev_window_size; 
    u64 delta; 
    u64 *curr_runnable_sum = &rq->wrq.curr_runnable_sum; 
    u64 *prev_runnable_sum = &rq->wrq.prev_runnable_sum; 
    u64 *nt_curr_runnable_sum = &rq->wrq.nt_curr_runnable_sum; 
    u64 *nt_prev_runnable_sum = &rq->wrq.nt_prev_runnable_sum; 
    bool new_task; 
    struct walt_related_thread_group *grp; 
    int cpu = rq->cpu; 
    u32 old_curr_window = p->wts.curr_window; 
 
    new_window = mark_start < window_start; 
    if (new_window) 
        full_window = (window_start - mark_start) >= window_size; 
 
    /* 处理每个任务的窗口翻转。 我们不关心空闲任务。*/ 
    if (!is_idle_task(p)) { 
        if (new_window) 
            /* 将 p->wts 的 curr_window 赋值给 prev_window,然后将 curr_window 清0 */ 
            rollover_task_window(p, full_window); 
    } 
 
    new_task = is_new_task(p); //运行时间小于5个窗口的任务 
 
    /* p是curr任务并且有了个新窗口才执行 */ 
    if (p_is_curr_task && new_window) { 
        /* rq的一些成员,prev_*_sum=curr_*_sum, 然后将 curr_*_sum 赋值为0 */ 
        rollover_cpu_window(rq, full_window); 
        rollover_top_tasks(rq, full_window); //这里面已经更新了rq->wrq.curr_table ############ 
    } 
 
    /* 判断是否需要记录 */ 
    if (!account_busy_for_cpu_time(rq, p, irqtime, event)) 
        goto done; 
    /*----下面就是需要计算的了----*/ 
 
    grp = p->wts.grp; 
    if (grp) { 
        struct group_cpu_time *cpu_time = &rq->wrq.grp_time; 
        /* 注意:指向更改了! */ 
        curr_runnable_sum = &cpu_time->curr_runnable_sum; 
        prev_runnable_sum = &cpu_time->prev_runnable_sum; 
 
        nt_curr_runnable_sum = &cpu_time->nt_curr_runnable_sum; 
        nt_prev_runnable_sum = &cpu_time->nt_prev_runnable_sum; 
    } 
 
    if (!new_window) { 
        /* 
         * account_busy_for_cpu_time() = 1 所以忙时间需要计入当前窗口。  
         * 没有翻转,因为我们没有启动一个新窗口。 这方面的一个例子是当 
         * 任务开始执行然后在同一窗口内休眠时。 
         */ 
        if (!irqtime || !is_idle_task(p) || cpu_is_waiting_on_io(rq)) 
            delta = wallclock - mark_start; 
        else 
            delta = irqtime; 
        delta = scale_exec_time(delta, rq); //等于直接return delta 
        *curr_runnable_sum += delta; 
        if (new_task) 
            *nt_curr_runnable_sum += delta; 
 
        if (!is_idle_task(p)) { 
            p->wts.curr_window += delta; 
            p->wts.curr_window_cpu[cpu] += delta; 
        } 
 
        goto done; 
    } 
    /*----下面就是有一个新窗口的情况了----*/ 
 
    if (!p_is_curr_task) { 
        /* 
         * account_busy_for_cpu_time() = 1 所以忙时间需要计入当前窗口。 
         * 一个新窗口也已启动,但 p 不是当前任务,因此窗口不会翻转  
         * - 只需拆分并根据需要将计数分为 curr 和 prev。 仅在为当前任 
         * 务处理新窗口时才会翻转窗口。 
         * 
         * irqtime 不能由不是当前正在运行的任务的任务计算。 
         */ 
 
        if (!full_window) { 
            /* 一个完整的窗口还没有过去,计算对上一个完成的窗口的部分贡献。*/ 
            delta = scale_exec_time(window_start - mark_start, rq); 
            p->wts.prev_window += delta; 
            p->wts.prev_window_cpu[cpu] += delta; 
        } else { 
            /* 由于至少一个完整的窗口已经过去,对前一个窗口的贡献是一个完整的窗口(window_size) */ 
            delta = scale_exec_time(window_size, rq); 
            p->wts.prev_window = delta; 
            p->wts.prev_window_cpu[cpu] = delta; 
        } 
 
        *prev_runnable_sum += delta; 
        if (new_task) 
            *nt_prev_runnable_sum += delta; 
 
        /* 只占当前窗口的一部分繁忙时间 */ 
        delta = scale_exec_time(wallclock - window_start, rq); 
        *curr_runnable_sum += delta; 
        if (new_task) 
            *nt_curr_runnable_sum += delta; 
 
        p->wts.curr_window = delta; /*对当前窗的贡献直接复制给当前窗*/ 
        p->wts.curr_window_cpu[cpu] = delta; 
 
        goto done; 
    } 
    /*----下面p是当前任务的情况了----*/ 
 
    if (!irqtime || !is_idle_task(p) || cpu_is_waiting_on_io(rq)) { 
        /* 
         * account_busy_for_cpu_time() = 1 所以忙时间需要计入当前窗口。 一个新窗口已经启动,  
         * p 是当前任务,因此需要翻转。 如果以上三个条件中的任何一个为真,那么这个繁忙的时 
         * 间就不能算作 irqtime。 
         * 
         * 空闲任务的繁忙时间不需要计算。 
         * 
         * 一个例子是一个任务开始执行,然后在新窗口开始后休眠。 
         */ 
 
        if (!full_window) { 
            /* 一个完整的窗口还没有过去,计算对上一个完整的窗口的部分贡献。*/ 
            delta = scale_exec_time(window_start - mark_start, rq); //等效直接返回window_start - mark_start 
            if (!is_idle_task(p)) { 
                p->wts.prev_window += delta; 
                p->wts.prev_window_cpu[cpu] += delta; 
            } 
        } else { 
            /* 由于至少一个完整的窗口已经过去,对前一个窗口的贡献是完整的窗口(window_size)*/ 
            delta = scale_exec_time(window_size, rq); 
            if (!is_idle_task(p)) { 
                p->wts.prev_window = delta; 
                p->wts.prev_window_cpu[cpu] = delta; 
            } 
        } 
 
        /* 在这里通过覆盖 prev_runnable_sum 和 curr_runnable_sum 中的值来完成翻转。*/ 
        *prev_runnable_sum += delta; 
        if (new_task) 
            *nt_prev_runnable_sum += delta; 
 
        /* 计算在当前窗口忙时的一片时间 */ 
        delta = scale_exec_time(wallclock - window_start, rq); 
        *curr_runnable_sum += delta; 
        if (new_task) 
            *nt_curr_runnable_sum += delta; 
 
        if (!is_idle_task(p)) { 
            p->wts.curr_window = delta; 
            p->wts.curr_window_cpu[cpu] = delta; 
        } 
 
        goto done; 
    } 
    /*---- 下面就对应 irqtime && is_idle_task(p) && !cpu_is_waiting_on_io(rq) 的情况了,并且累积上面的条件 ----*/ 
 
    if (irqtime) { 
        /* 
         * account_busy_for_cpu_time() = 1 所以忙时间需要计入当前窗口。 
         * 一个新窗口已经启动,p 是当前任务,因此需要翻转。 当前任务必 
         * 须是空闲任务,因为不为其他任何任务计算irqtime。 
         * 
         * 空闲一段时间后,每次我们处理 IRQ 活动时都会计算 Irqtime,因 
         * 此我们知道 IRQ 繁忙时间为 wallclock - irqtime。 
         */ 
 
        SCHED_BUG_ON(!is_idle_task(p)); 
        mark_start = wallclock - irqtime; 
 
        /* 
         * 滚动窗口。 如果 IRQ 繁忙时间只是在当前窗口中,那么这就是所有需要计算的。 
         */ 
        if (mark_start > window_start) { 
            *curr_runnable_sum = scale_exec_time(irqtime, rq); //等效于直接返回irqtime,因为是idle线程,之前应该是0的 
            return; 
        } 
        /*---下面是ms<=ws---*/ 
 
        /* 
         * IRQ 繁忙时间跨越多个窗口。 先处理当前窗口开始前的忙时间。 
         */ 
        delta = window_start - mark_start; 
        if (delta > window_size) 
            delta = window_size; 
        delta = scale_exec_time(delta, rq); 
        *prev_runnable_sum += delta; //这直接加不会超过一个窗的大小吗? 
 
        /* Process the remaining IRQ busy time in the current window.  处理当前窗口中剩余的 IRQ 忙时间。*/ 
        delta = wallclock - window_start; 
        rq->wrq.curr_runnable_sum = scale_exec_time(delta, rq); 
 
        return; 
    } 
 
done: 
    if (!is_idle_task(p)) 
        update_top_tasks(p, rq, old_curr_window, new_window, full_window); 
}

值更新当前窗口和前一个窗口的busy时间,主要用于更新任务的: p->wts.curr_window、p->wts.curr_window_cpu[cpu],更新rq 的 rq->wrq.curr_runnable_sum、rq->wrq.prev_runnable_sum,若是一个walt认为的新任务,还更新 rq->wrq.nt_curr_runnable_sum、rq->wrq.nt_prev_runnable_sum。然后是更新 top-task 的一些成员

下面分别是对 task、cpu、top_tasks 维护的 window 进行更新。有一个新的窗口到来时更新,若更新时已经经历了一个或多个完整的window,那么对prev和curr window 相关的描述结构进行清理备用。

static u32 empty_windows[NR_CPUS]; 
/* 将 p->wts 的 curr_window 赋值给 prev_window,然后将 curr_window 清0 */ 
static void rollover_task_window(struct task_struct *p, bool full_window) 
{ 
    u32 *curr_cpu_windows = empty_windows; //数组,每个cpu一个 
    u32 curr_window; 
    int i; 
 
    /* Rollover the sum */ 
    curr_window = 0; 
 
    /* 若经历了一个full_window, prev和curr window都清理待用 */ 
    if (!full_window) { 
        curr_window = p->wts.curr_window; 
        curr_cpu_windows = p->wts.curr_window_cpu; 
    } 
 
    p->wts.prev_window = curr_window; 
    p->wts.curr_window = 0; 
 
    /* Roll over individual CPU contributions 滚动每个 CPU 的贡献 */ 
    for (i = 0; i < nr_cpu_ids; i++) { 
        p->wts.prev_window_cpu[i] = curr_cpu_windows[i]; 
        p->wts.curr_window_cpu[i] = 0; 
    } 
 
    if (is_new_task(p)) 
        p->wts.active_time += task_rq(p)->wrq.prev_window_size; //active_time 的唯一更新位置, walt认为的新任务 
}

清理的是任务的 p->wts.prev_window_cpu、p->wts.curr_window、p->wts.prev_window_cpu[]、p->wts.curr_window_cpu[]

/* 
 * rq的一些成员,prev_*_sum=curr_*_sum, 然后将 curr_*_sum 赋值为0,将curr赋值给prev, 
 * 若是有经历了多个窗口curr和prev窗口都需要清理待用。 
 */ 
static void rollover_cpu_window(struct rq *rq, bool full_window) 
{ 
    u64 curr_sum = rq->wrq.curr_runnable_sum; 
    u64 nt_curr_sum = rq->wrq.nt_curr_runnable_sum; 
    u64 grp_curr_sum = rq->wrq.grp_time.curr_runnable_sum; 
    u64 grp_nt_curr_sum = rq->wrq.grp_time.nt_curr_runnable_sum; 
 
    if (unlikely(full_window)) { 
        curr_sum = 0; 
        nt_curr_sum = 0; 
        grp_curr_sum = 0; 
        grp_nt_curr_sum = 0; 
    } 
 
    rq->wrq.prev_runnable_sum = curr_sum; 
    rq->wrq.nt_prev_runnable_sum = nt_curr_sum; 
    rq->wrq.grp_time.prev_runnable_sum = grp_curr_sum; 
    rq->wrq.grp_time.nt_prev_runnable_sum = grp_nt_curr_sum; 
 
    rq->wrq.curr_runnable_sum = 0; 
    rq->wrq.nt_curr_runnable_sum = 0; 
    rq->wrq.grp_time.curr_runnable_sum = 0; 
    rq->wrq.grp_time.nt_curr_runnable_sum = 0; 
}

清理的是 rq->wrq 的 和 rq->wrq.grp_time 的 prev_runnable_sum、curr_runnable_sum、nt_prev_runnable_sum、nt_curr_runnable_sum

static void rollover_top_tasks(struct rq *rq, bool full_window) 
{ 
    /* 跟踪的是2个,构成一个环形数组 */ 
    u8 curr_table = rq->wrq.curr_table; 
    u8 prev_table = 1 - curr_table; 
    int curr_top = rq->wrq.curr_top; 
 
    /*将prev window的数据结构清理后待用*/ 
    clear_top_tasks_table(rq->wrq.top_tasks[prev_table]); //memset(arg, 0, NUM_LOAD_INDICES * sizeof(u8)); 
    clear_top_tasks_bitmap(rq->wrq.top_tasks_bitmap[prev_table]);//将bit数组的内容清0,然后将 NUM_LOAD_INDICES bit设置为1 
 
    /*若是已经经历了多个window,那么之前标记的curr window也是旧窗口了,需要清理待用*/ 
    if (full_window) { 
        curr_top = 0; 
        clear_top_tasks_table(rq->wrq.top_tasks[curr_table]); 
        clear_top_tasks_bitmap(rq->wrq.top_tasks_bitmap[curr_table]); 
    } 
 
    /*两个window的下标进行翻转,curr-->prev,prev-->curr*/ 
    rq->wrq.curr_table = prev_table; 
    rq->wrq.prev_top = curr_top; 
    rq->wrq.curr_top = 0; 
}

清理的是 rq->wrq 的 top_task 相关的成员。

然后调用 account_busy_for_cpu_time 判断清理后任务的和cpu的是否还需要更新上去

/* update_cpu_busy_time-->this, 传参(rq, p, irqtime, event) */ 
static int account_busy_for_cpu_time(struct rq *rq, struct task_struct *p, u64 irqtime, int event) 
{ 
    if (is_idle_task(p)) { 
        /* TASK_WAKE && TASK_MIGRATE is not possible on idle task!  idle task不可能出现唤醒和迁移 */ 
        if (event == PICK_NEXT_TASK) 
            return 0; 
 
        /* PUT_PREV_TASK, TASK_UPDATE && IRQ_UPDATE are left */ 
        return irqtime || cpu_is_waiting_on_io(rq); 
    } 
 
    if (event == TASK_WAKE) 
        return 0; 
 
    if (event == PUT_PREV_TASK || event == IRQ_UPDATE) 
        return 1; 
 
    /* 
     * TASK_UPDATE can be called on sleeping task, when its moved between related groups 
     * TASK_UPDATE 当它在相关组之间移动时可能在睡眠的任务上调用, 
     */ 
    if (event == TASK_UPDATE) { 
        if (rq->curr == p) 
            return 1; 
 
        return p->on_rq ? SCHED_FREQ_ACCOUNT_WAIT_TIME : 0; //在rq上和或正在迁移是1,但是冒号前后都是0 
    } 
 
    /* TASK_MIGRATE, PICK_NEXT_TASK left */ 
    return SCHED_FREQ_ACCOUNT_WAIT_TIME; //0 
}

top_task 维护的窗口更新:

/*  
 * update_cpu_busy_time-->this 若p不是idle任务,就调用,传参(p, rq, old_curr_window, new_window, full_window)  
 * @ old_curr_window:取自 p->wts.curr_window,表示p在窗口翻转前在当前窗口的运行时间 
 * @ new_window:bool值,若ms<ws为真 
 * @ full_window:bool值,若ws-ms>window_size为真 
 */ 
static void update_top_tasks(struct task_struct *p, struct rq *rq, u32 old_curr_window, int new_window, bool full_window) 
{ 
    /* 只使用两个窗口进行跟踪,当前是0,perv就是1,当前是1,prev就是0,两个数据结构构成一个环形缓存区 */ 
    u8 curr = rq->wrq.curr_table; 
    u8 prev = 1 - curr; 
    u8 *curr_table = rq->wrq.top_tasks[curr]; 
    u8 *prev_table = rq->wrq.top_tasks[prev]; 
    int old_index, new_index, update_index; 
    u32 curr_window = p->wts.curr_window; 
    u32 prev_window = p->wts.prev_window; 
    bool zero_index_update; 
 
    /* 两个窗的运行时间相等或新窗口还没有到来 */ 
    if (old_curr_window == curr_window && !new_window) 
        return; 
 
    /* 在一个窗中运行的时间越长,index就越大, 参数是一个窗口中的运行时长*/ 
    old_index = load_to_index(old_curr_window); 
    new_index = load_to_index(curr_window); 
 
    if (!new_window) { 
        zero_index_update = !old_curr_window && curr_window; 
        if (old_index != new_index || zero_index_update) { 
            if (old_curr_window) 
                curr_table[old_index] -= 1; //上一个窗口的累计值衰减 
            if (curr_window) 
                curr_table[new_index] += 1; //新窗口的累计值增加 
            if (new_index > rq->wrq.curr_top) 
                rq->wrq.curr_top = new_index; //更新rq->wrq.curr_top成员 
        } 
 
        if (!curr_table[old_index]) 
            __clear_bit(NUM_LOAD_INDICES - old_index - 1, rq->wrq.top_tasks_bitmap[curr]); //这个bit数组表示此运行时间下有没有计数值 
 
        if (curr_table[new_index] == 1) 
            __set_bit(NUM_LOAD_INDICES - new_index - 1, rq->wrq.top_tasks_bitmap[curr]); 
 
        return; 
    } 
    /*---下面是new_window!=0的情况了---*/ 
 
    /* 
     * 对于此任务来说窗口已经翻转。 当我们到达这里时,curr/prev 交换已经发生。  
     * 所以我们需要对新索引使用 prev_window 。 
     */ 
    update_index = load_to_index(prev_window); 
 
    if (full_window) { //至少有一个满窗 
        /* 
         * 这里有两个案例。 要么'p' 运行了整个窗口,要么根本不运行。 在任何一种情况下, 
         * prev 表中都没有条目。 如果 'p' 运行整个窗口,我们只需要在 prev 表中创建一个 
         * 新条目。 在这种情况下,update_index 将对应于 sched_ravg_window,因此我们可 
         * 以无条件地更新顶部索引。 
         */ 
        if (prev_window) { 
            prev_table[update_index] += 1; 
            rq->wrq.prev_top = update_index; 
        } 
 
        if (prev_table[update_index] == 1) 
            __set_bit(NUM_LOAD_INDICES - update_index - 1, rq->wrq.top_tasks_bitmap[prev]); 
    } else { //产生了新窗口,但是还没达到一个满窗 
        zero_index_update = !old_curr_window && prev_window; 
        if (old_index != update_index || zero_index_update) { 
            if (old_curr_window) 
                prev_table[old_index] -= 1; 
 
            prev_table[update_index] += 1; 
 
            if (update_index > rq->wrq.prev_top) 
                rq->wrq.prev_top = update_index; 
 
            /* 减为0是清理对应bit,首次设置为1时设置相应bit。top_tasks_bitmap[]在任务迁移时有使用 */ 
            if (!prev_table[old_index]) 
                __clear_bit(NUM_LOAD_INDICES - old_index - 1, rq->wrq.top_tasks_bitmap[prev]); 
            if (prev_table[update_index] == 1) 
                __set_bit(NUM_LOAD_INDICES - update_index - 1, rq->wrq.top_tasks_bitmap[prev]); 
        } 
    } 
 
    if (curr_window) { 
        curr_table[new_index] += 1; 
 
        if (new_index > rq->wrq.curr_top) 
            rq->wrq.curr_top = new_index; 
 
        if (curr_table[new_index] == 1) 
            __set_bit(NUM_LOAD_INDICES - new_index - 1, rq->wrq.top_tasks_bitmap[curr]); 
    } 
}

top_tasks 的维护中也使用到了桶,新窗运行时间对应的 curr_table[]成员加1,之前窗口运行时间对应的 prev_table[] 成员减1。

9. update_task_pred_demand 函数

/* 
 * 在窗口翻转时计算任务的预测需求。如果任务当前窗口繁忙时间超过预测需求,则在此处更新以反映任务需求。 
 */ 
void update_task_pred_demand(struct rq *rq, struct task_struct *p, int event) 
{ 
    u32 new, old; 
    u16 new_scaled; 
 
    if (!sched_predl) //1 
        return; 
 
    if (is_idle_task(p)) 
        return; 
 
    if (event != PUT_PREV_TASK && event != TASK_UPDATE && 
            (!SCHED_FREQ_ACCOUNT_WAIT_TIME || (event != TASK_MIGRATE && event != PICK_NEXT_TASK))) 
        return; 
 
    /* 
     * 当它在相关组之间移动时,TASK_UPDATE 可以在睡眠任务上调用。 
     */ 
    if (event == TASK_UPDATE) { 
        if (!p->on_rq && !SCHED_FREQ_ACCOUNT_WAIT_TIME) 
            return; 
    } 
 
    new = calc_pred_demand(p); 
    old = p->wts.pred_demand; 
 
    if (old >= new) 
        return; 
    /*---下面就是 new > old 的情况---*/ 
 
    new_scaled = scale_demand(new); //new/window_size*1024 
    /* p是on rq的状态并且不是已经被throttle的deadline任务 */ 
    if (task_on_rq_queued(p) && (!task_has_dl_policy(p) || !p->dl.dl_throttled)) 
        fixup_walt_sched_stats_common(rq, p, p->wts.demand_scaled, new_scaled); 
 
    p->wts.pred_demand = new; 
    p->wts.pred_demand_scaled = new_scaled; 
}

注意,这里再次调用了 fixup_walt_sched_stats_common,在 walt_update_task_ravg 函数中,在 update_history 中已经调用过一次,进入条件也相同,也是p在队列上。

static inline u32 calc_pred_demand(struct task_struct *p) 
{ 
    /* 预测的需求比当前窗口的大,就返回预测的需求 */ 
    if (p->wts.pred_demand >= p->wts.curr_window) 
        return p->wts.pred_demand; 
 
    return get_pred_busy(p, busy_to_bucket(p->wts.curr_window), p->wts.curr_window); 
}

get_pred_busy 和 busy_to_bucket 两个函数上面都有列出。

10. run_walt_irq_work 函数

static inline void run_walt_irq_work(u64 old_window_start, struct rq *rq) //walt.c 
{ 
    u64 result; 
 
    /*若是还是同一个窗,直接退出*/ 
    if (old_window_start == rq->wrq.window_start) 
        return; 
 
    /*  
     * atomic64_cmpxchg(*ptr, old, new) 函数功能是:将old和ptr指向的内容比较,如果相等, 
     * 则将new写入到ptr指向的内存中,并返回old,如果不相等,则返回ptr指向的内容。 
     */ 
    result = atomic64_cmpxchg(&walt_irq_work_lastq_ws, old_window_start, rq->wrq.window_start); 
    if (result == old_window_start) { 
        walt_irq_work_queue(&walt_cpufreq_irq_work); //触发回调 walt_irq_work(),构成一个"内核线程",循环往复执行 
 
        trace_walt_window_rollover(rq->wrq.window_start); 
    } 
}

walt_irq_work_queue 会触发 walt_irq_work() 被调用,这个函数中又会调用 walt_update_task_ravg,walt_update_task_ravg 函数会在每个tick中调用,这里这样实现可能是针对没有tick的场景使用。

其中Trace:

trace_walt_window_rollover(rq->wrq.window_start);

参数原型:

(u64 window_start)

打印内容:

//20ms间隔执行一次 
<idle>-0     [002] d..2 48262.320451: walt_window_rollover: window_start=48262548000001 
<idle>-0     [001] d.h2 48262.340457: walt_window_rollover: window_start=48262568000001

字段解析:

window_start 就是打印 rq->wrq.window_start 的记录的时间值,单位是ns.

四、总结

1. WALT负载计算算法是基于窗口的,对window有一个rollover的操作,只跟踪curr和prev两个窗口,curr窗口的下标由 wrq.curr_table 指向,两个窗口构成一个唤醒缓冲区,prev和curr进行不断切换。

2. walt_update_task_ravg 函数通过其 event 成员决定对哪些事件计算负载,再根据其调用路径和其调用函数对是否是在rq上,是否是p=rq->curr可以判断统计的是哪部分的负载。

3. 预测负载这块,对于任务和CPU都使用了桶,任务是10个桶,对于cpu的curr和prev两个窗口分别是1000个成员,命中累加,不命中衰减。

4. walt_update_task_ravg 在tick的调用路径中有调用,应该是为了无tick情况下walt仍然能正常工作,使用irq_work构成一个内核线程以一个窗口的周期来更新窗口。

五、补充

1. task util的获取:task_util() WALT: p->wts.demand_scaled;PELT: p->se.avg.util_avg

2. cpu util的获取:cpu_util_cum() WALT: rq->wrq.cum_window_demand_scaled;PELT: rq->cfs.avg.util_avg

3. task_util() 函数

static inline unsigned long task_util(struct task_struct *p) 
{ 
#ifdef CONFIG_SCHED_WALT 
    if (likely(!walt_disabled && sysctl_sched_use_walt_task_util)) 
        return p->wts.demand_scaled; 
#endif 
    return READ_ONCE(p->se.avg.util_avg); 
}

本文参考链接:https://www.cnblogs.com/hellokitty2/p/15376806.html