<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=845&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / Gentoo 之 WALT负载计算源码分析]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=845</link>
		<description><![CDATA[Gentoo 之 WALT负载计算源码分析 最近发表的帖子。]]></description>
		<lastBuildDate>Wed, 27 Mar 2024 13:16:17 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[Gentoo 之 WALT负载计算源码分析]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=964#p964</link>
			<description><![CDATA[<p>一、WALT简介</p><p>&#160; &#160; WALT(Windows-Assist Load Tracing)，从字面意思来看，是以window作为辅助项来跟踪cpu load，用来表现cpu当前的loading情况，用于后续任务调度、迁移、负载均衡等功能。在 load 的基础上，添加对于demand的记录用于之后的预测。只统计runable和running time。<br />&#160; &#160; WALT由Qcom研发，主要用于移动设备对性能功耗要求比较高的场景，在与用户交互时需要尽快响应，要能及时反应负载的增加和减少以驱动频点及时的变化。当前的PELT负载跟踪算法更主要的是体现负载的连续性，对于突变性质的负载的反应不是很友好，负载上升慢，下降也慢。<br />&#160; &#160; 打开 CONFIG_SCHED_WALT 使能此feature。<br />&#160; &#160; 辅助计算项 window 的划分方法是将系统自启动开始以一定时间作为一个周期，分别统计不同周期内 Task 的 Loading 情况，并将其更新到Runqueue中；目前 Kernel 中是设置的一个 window 的大小是20ms，统计 5 个window内的Loading情况，当然，这也可以根据具体的项目需求进行配置。</p><p>二、相关数据结构</p><p>(1) 嵌入在 task_struct 中的 walt_task_struct</p><div class="codebox"><pre><code>/*
 * &#039;mark_start&#039; 标记窗口内事件的开始（任务唤醒、任务开始执行、任务被抢占）
 * &#039;sum&#039; 表示任务在当前窗口内的可运行程度。它包含运行时间和等待时间，并按频率进行缩放。//就是在当前窗口的运行时间吧
 * &#039;sum_history&#039; 跟踪在之前的 RAVG_HIST_SIZE 窗口中看到的 &#039;sum&#039; 的历史记录。任务完全休眠的窗口将被忽略。
 * &#039;demand&#039; 表示在以前的 sysctl_sched_ravg_hist_size 窗口中看到的最大总和(根据window_policy选的)。 &#039;demand&#039;可以为任务驱动频率的改变。#######
 * &#039;curr_window_cpu&#039; 代表任务对当前窗口各CPU上cpu繁忙时间的贡献
 * &#039;prev_window_cpu&#039; 表示任务对前一个窗口中各个 CPU 上的 cpu 繁忙时间的贡献
 * &#039;curr_window&#039; 表示 curr_window_cpu 中所有条目的总和
 * &#039;prev_window&#039; 代表 prev_window_cpu 中所有条目的总和
 * &#039;pred_demand&#039; 代表任务当前预测的cpu繁忙时间
 * &#039;busy_buckets&#039; 将历史繁忙时间分组到用于预测的不同桶中
 * &#039;demand_scaled&#039; 表示任务的需求缩放到 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;
    ...
} </code></pre></div><p>(2) 嵌入在 rq 中的 walt_rq</p><div class="codebox"><pre class="vscroll"><code>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-&gt;wrq.top_tasks[]中前一个窗最大值的下标
    int            curr_top; //是rq-&gt;wrq.top_tasks[]中当前窗最大值的下标
    u64            cycles;
    ...
};

struct walt_sched_stats {
    int nr_big_tasks;
    u64 cumulative_runnable_avg_scaled; //只统计runnable任务的，在update_window_start中赋值给rq-&gt;wrq.cum_window_demand_scaled
    u64 pred_demands_sum_scaled;
    unsigned int nr_rtg_high_prio_tasks;
}; </code></pre></div><p>三、负载计算函数</p><p>1. walt算法负载计算入口函数</p><div class="codebox"><pre class="vscroll"><code> /* 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-&gt;wrq.window_start || p-&gt;wts.mark_start == wallclock)
        return;

    lockdep_assert_held(&amp;rq-&gt;lock);

    /* 更新ws，返回最新的ws */
    old_window_start = update_window_start(rq, wallclock, event);

    /* 对应还没初始化的情况, ws是per-rq的，ms是per-task的，wc是全局的 */
    if (!p-&gt;wts.mark_start) {
        update_task_cpu_cycles(p, cpu_of(rq), wallclock);
        goto done;
    }
    /*更新 rq-&gt;wrq.task_exec_scale 和 p-&gt;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 &amp;&amp; p-&gt;state) {
        p-&gt;wts.iowaited = p-&gt;in_iowait;
    }

    trace_sched_update_task_ravg(p, rq, event, wallclock, irqtime, &amp;rq-&gt;wrq.grp_time);

    trace_sched_update_task_ravg_mini(p, rq, event, wallclock, irqtime, &amp;rq-&gt;wrq.grp_time);

done:
    /* 更新per-task的 ms，ms是在动态变化的 */
    p-&gt;wts.mark_start = wallclock;

    /*构成一个内核线程，每个窗口执行一次*/
    run_walt_irq_work(old_window_start, rq);
}</code></pre></div><p>此函数中的两个trace解析：<br />(1) trace_sched_update_task_ravg(p, rq, event, wallclock, irqtime, &amp;rq-&gt;wrq.grp_time);</p><p>参数原型：<br />(struct task_struct *p, struct rq *rq, enum task_event evt, u64 wallclock, u64 irqtime, struct group_cpu_time *cpu_time)</p><p>打印内容：</p><div class="codebox"><pre><code>&lt;idle&gt;-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 13 </code></pre></div><p>字段解析：<br />wc：为参数4 wallclock;<br />ws: 为 window_start，取自 rq-&gt;wrq.window_start; <br />delta：取自 wallclock - rq-&gt;wrq.window_start 的差值。<br />event：task_event_names[参数3], 字符串表示的事件类型<br />cpu：取自 rq-&gt;cpu<br />cur_freq：取自 rq-&gt;wrq.task_exec_scale，update_task_rq_cpu_cycles()中，若不使用 use_cycle_counter，赋值为 cpu_capaticy * (freq / maxfreq)<br />cur_pid: 取自 rq-&gt;curr-&gt;pid<br />task：取自参数1 p 的 p-&gt;pid<br />kworker/u16:5：取自参数1 p 的 p-&gt;comm<br />ms：是 mark_start 取自 p-&gt;wts.mark_start<br />delta：打印中有两个同名的delta，这是第二个，取自 wallclock - p-&gt;wts.mark_start<br />demand：取自 p-&gt;wts.demand，单位是ns，就是根据 p-&gt;wts.sum 取平均或和最近窗口两者之间的最大值<br />coloc_demand：取自 p-&gt;wts.coloc_demand<br />sum：取自 p-&gt;wts.sum，表示最近一个窗口运行时间之和，单位ns，在将其更新到history数组后，清0.<br />irqtime：取自参数4<br />pred_demand：取自 p-&gt;wts.pred_demand<br />rq_cs：取自 rq-&gt;wrq.curr_runnable_sum 表示<br />rq_ps：取自 rq-&gt;wrq.prev_runnable_sum 表示<br />cur_window：取自 p-&gt;wts.curr_window，表示任务在当前窗口中所有cpu上的运行时间之和，是后面数组的累加。<br />(0 136431 573963 0 219736 0 0 0 )：取自 p-&gt;wts.curr_window_cpu per-cpu的，表示任务在当前窗口中在每个cpu上运行的时间<br />prev_window：取自 p-&gt;wts.prev_window<br />(222138 156646 973556 0 219811 566790 0 0 )：取自 p-&gt;wts.prev_window_cpu 也是per-cpu的，表示任务在前一个窗口中在每个cpu上运行的时间<br />nt_cs：取自 rq-&gt;wrq.nt_curr_runnable_sum nt应该表示的是new task的缩写<br />nt_ps：取自 rq-&gt;wrq.nt_prev_runnable_sum<br />active_time：取自 p-&gt;wts.active_time is_new_task()中判断它，唯一更新位置rollover_task_window()中调用is_new_task()判断是新任务时 p-&gt;wts.active_time += task_rq(p)-&gt;wrq.prev_window_size;<br />grp_cs：取自 cpu_time ? cpu_time-&gt;curr_runnable_sum : 0 根据最后一个参数来判断是更新rq的还是更新rtg group的<br />grp_ps：取自 cpu_time ? cpu_time-&gt;prev_runnable_sum : 0<br />grp_nt_cs：取自 cpu_time ? cpu_time-&gt;nt_curr_runnable_sum : 0<br />grp_nt_ps：取自 cpu_time ? cpu_time-&gt;nt_prev_runnable_sum : 0<br />curr_top：取自 rq-&gt;wrq.curr_top 记录的是当前窗口中 rq-&gt;wrq.top_tasks[]中最大值的下标<br />prev_top：取自 rq-&gt;wrq.prev_top 记录的是前一个窗口中 rq-&gt;wrq.top_tasks[]中最大值的下标</p><p>(2) trace_sched_update_task_ravg_mini(p, rq, event, wallclock, irqtime, &amp;rq-&gt;wrq.grp_time);</p><p>参数原型：<br />(struct task_struct *p, struct rq *rq, enum task_event evt, u64 wallclock, u64 irqtime, struct group_cpu_time *cpu_time)</p><p>打印内容：</p><p>&lt;idle&gt;-0&#160; &#160; &#160;[005] d..2 280546.887141: sched_update_task_ravg_mini: wc 112233604355205 ws 112233596000001 delta 8355204<br />event PUT_PREV_TASK cpu 5 task 0 (swapper/5) ms 112233604337548 delta 17657 demand 2400000 rq_cs 1374618 rq_ps 1237818<br />cur_window 0 prev_window 0 grp_cs 0 grp_ps 0</p><p>字段解析：<br />wc：取自参数 wallclock<br />ws：取自 rq-&gt;wrq.window_start<br />delta：取自 wallclock - rq-&gt;wrq.window_start<br />event：取自 task_event_names[evt]<br />cpu：取自 rq-&gt;cpu<br />task：取自 p-&gt;pid<br />swapper/5：取自 p-&gt;comm<br />ms：取自 p-&gt;wts.mark_start<br />delta：两个同名，这是第二个，取自 wallclock - p-&gt;wts.mark_start<br />demand：取自 p-&gt;wts.demand<br />rq_cs：取自 rq-&gt;wrq.curr_runnable_sum<br />rq_ps：取自 rq-&gt;wrq.prev_runnable_sum<br />cur_window：取自 p-&gt;wts.curr_window<br />prev_window：取自 p-&gt;wts.prev_window<br />grp_cs：取自 cpu_time ? cpu_time-&gt;curr_runnable_sum : 0<br />grp_ps：取自 cpu_time ? cpu_time-&gt;prev_runnable_sum : 0</p><p>2. walt_update_task_ravg 的调用路径</p><div class="codebox"><pre class="vscroll"><code>tick_setup_sched_timer //tick_sched.c timer到期回调函数中指定 tick_sched_timer
        update_process_times //time.c tick中断中调用
            scheduler_tick //core.c 周期定时器中断，传参(rq-&gt;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--&gt;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-&gt;on_rq = TASK_ON_RQ_MIGRATING
            fixup_busy_time //walt.c 连续调用三次，分别传参 (task_rq(p)-&gt;curr, task_rq(p), TASK_UPDATE, wallclock, 0)和(dest_rq-&gt;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-&gt;curr, rq, TASK_UPDATE, wallclock, 0)
sync_cgroup_colocation //walt.c cpu_cgrp_subsys.attach=cpu_cgroup_attach--&gt;walt_schedgp_attach中对每一个cpuset都调用
sched_group_id_write //qc_vas.c 对应/proc/&lt;pid&gt;/sched_group_id
    __sched_set_group_id //传参group_id=0才调用
        remove_task_from_group //walt.c 传参(rq, p-&gt;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-&gt;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，构成一个&quot;内核线程不&quot;停执行
    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-&gt;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-&gt;curr, rq, TASK_UPDATE, wallclock, 0)和(p, rq, TASK_WAKE, wallclock, 0)
                walt_update_task_ravg</code></pre></div><p>walt_update_task_ravg 通过参数 event 可以控制哪些事件不更新负载。</p><p>3. update_window_start 函数</p><div class="codebox"><pre><code>/* 唯一调用路径：walt_update_task_ravg --&gt; 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-&gt;wrq.window_start;

    delta = wallclock - rq-&gt;wrq.window_start;
    if (delta &lt; 0) {
        printk_deferred(&quot;WALT-BUG CPU%d; wallclock=%llu is lesser than window_start=%llu&quot;, rq-&gt;cpu, wallclock, rq-&gt;wrq.window_start);
        SCHED_BUG_ON(1);
    }

    /* sched_ravg_window 默认是20ms, 不足一个窗口就不更新，直接退出*/
    if (delta &lt; sched_ravg_window)
        return old_window_start;

    /* 下面是delta大于一个window的，计算历经的整窗的个数 */
    nr_windows = div64_u64(delta, sched_ravg_window);
    rq-&gt;wrq.window_start += (u64)nr_windows * (u64)sched_ravg_window; /* 更新ws */

    rq-&gt;wrq.cum_window_demand_scaled = rq-&gt;wrq.walt_stats.cumulative_runnable_avg_scaled;
    rq-&gt;wrq.prev_window_size = sched_ravg_window;

    return old_window_start;
}</code></pre></div><p>可以看到，rq-&gt;wrq.window_start、rq-&gt;wrq.cum_window_demand_scaled 是最先更新的。然后返回旧的 window_start，</p><p>4. update_task_cpu_cycles 函数</p><div class="codebox"><pre><code>static void update_task_cpu_cycles(struct task_struct *p, int cpu, u64 wallclock) //walt.c
{
    if (use_cycle_counter)
        p-&gt;wts.cpu_cycles = read_cycle_counter(cpu, wallclock);
}</code></pre></div><p>在 p-&gt;wts.mark_start 为0的时候，调用这个函数，应该是做初始化的。</p><p>5. update_task_rq_cpu_cycles 函数<br />&#039;</p><div class="codebox"><pre class="vscroll"><code>/* 唯一调用路径 walt_update_task_ravg --&gt; 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(&amp;rq-&gt;lock);

    if (!use_cycle_counter) {
        /* freq / maxfreq * cpu_capacity, arch_scale_cpu_capacity 为函数 topology_get_cpu_scale */
        rq-&gt;wrq.task_exec_scale = DIV64_U64_ROUNDUP(cpu_cur_freq(cpu) * arch_scale_cpu_capacity(cpu), rq-&gt;wrq.cluster-&gt;max_possible_freq);
        return;
    }

    cur_cycles = read_cycle_counter(cpu, wallclock); /*return rq-&gt;wrq.cycles;*/

    /*
     * 如果当前任务是空闲任务并且 irqtime == 0，CPU 确实空闲并且它的循环计数器可能没有增加。
     * 我们仍然需要估计的 CPU 频率来计算 IO 等待时间。 在这种情况下使用先前计算的频率。
     */
    if (!is_idle_task(rq-&gt;curr) || irqtime) {
        if (unlikely(cur_cycles &lt; p-&gt;wts.cpu_cycles)) //这应该是溢出了
            cycles_delta = cur_cycles + (U64_MAX - p-&gt;wts.cpu_cycles);
        else
            cycles_delta = cur_cycles - p-&gt;wts.cpu_cycles;

        cycles_delta = cycles_delta * NSEC_PER_MSEC;

        if (event == IRQ_UPDATE &amp;&amp; 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-&gt;wts.mark_start;
        SCHED_BUG_ON((s64)time_delta &lt; 0);

        /* (cycles_delta * cpu_capacity) / (time_delta * max_freq) = cycles_delta/time_delta * cpu_capacity/max_freq*/
        rq-&gt;wrq.task_exec_scale = DIV64_U64_ROUNDUP(cycles_delta * arch_scale_cpu_capacity(cpu), time_delta * rq-&gt;wrq.cluster-&gt;max_possible_freq);

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

    p-&gt;wts.cpu_cycles = cur_cycles;
}</code></pre></div><p>其中Trace:</p><p>trace_sched_get_task_cpu_cycles(cpu, event, cycles_delta, time_delta, p);</p><p>参数原型：</p><p>(int cpu, int event, u64 cycles, u64 exec_time, struct task_struct *p)</p><p>打印内容：</p><p>shell svc 7920-7921&#160; [006] d..4 53723.502493: sched_get_task_cpu_cycles: cpu=6 event=2 cycles=105682000000 exec_time=78229 freq=1350931 legacy_freq=2035200<br />max_freq=2035200 task=19304 (kworker/u16:5)</p><p>字段解析：</p><p>前4个字段直接来自参数，<br />freq：取自 cycles/exec_time, 其中 cycles 是乘以了 NSEC_PER_MSEC 的，exec_time 的单位是ns。<br />legacy_freq：取自 cpu_rq(cpu)-&gt;wrq.cluster-&gt;max_possible_freq，单位KHz<br />max_freq：取自 cpu_rq(cpu)-&gt;wrq.cluster-&gt;max_possible_freq * cpu_rq(cpu)-&gt;cpu_capacity_orig / SCHED_CAPACITY_SCALE<br />task：取自 p-&gt;pid<br />kworker/u16:5：取自 p-&gt;comm</p><p>6. update_history 解析</p><p>update_task_demand 中若判断不需要更新 task 的 p-&gt;wts.sum, 但是又有新窗口产生时，调用这个函数更新历史负载。</p><div class="codebox"><pre class="vscroll"><code>/*
 * 当一个任务的新窗口开始时调用，记录最近结束的窗口的 CPU 使用率。 通常&#039;samples&#039;应该是1。
 * 比如说，当一个实时任务同时运行而不抢占几个窗口时，它可以 &gt; 1，也就是说连续运行3个窗口才
 * 更新的话，samples就传3。
 *
 * update_task_demand()调用传参：(rq, p, p-&gt;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 = &amp;p-&gt;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 &#039;runtime&#039; 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 &gt;= 0; --widx, --ridx) {
        hist[widx] = hist[ridx];
        sum += hist[widx];  //此循环 sum = hist[4] + hist[3] + hist[2] + hist[1]
        if (hist[widx] &gt; max)
            max = hist[widx]; //max保存最近4个窗中的最大值
    }

    /*
     * 若samples=1, hist[0] = runtime
     * 若samples=2, hist[0] = runtime, hist[1] = runtime
     * ...
     */
    for (widx = 0; widx &lt; samples &amp;&amp; widx &lt; sched_ravg_hist_size; widx++) {
        hist[widx] = runtime; //hist[0]中存放的是最近的一个窗中运行的时间
        sum += hist[widx]; //sum再加上hist[0]
        if (hist[widx] &gt; max)
            max = hist[widx]; //max保存的是最近5个窗中最大的值了
    }

    /* 将p-&gt;wts.sum放入history数组后就清0了, 也说明这个sum是一个窗的sum值 */
    p-&gt;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-&gt;on_rq。 由于出队递减 walt stats 避免再次递减它。
     * 当窗口滚动时，累积窗口需求被重置为累积可运行平均值（来自运行队列上的任务的贡献）。如果当前任务已经出队，
     * 则它的需求不包括在累积可运行平均值中。所以将任务需求单独添加到累积窗口需求中。
     */
    /*这里增加的是rq上的统计值，不是per-entity的了*/
    if (!task_has_dl_policy(p) || !p-&gt;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-&gt;curr == p) {
            walt_fixup_cum_window_demand(rq, demand_scaled);
        }
    }

    /*赋值给per-entiry上的统计值，demand_scaled 对 p-&gt;wts.demand_scaled 的赋值一定要保证，这是walt负载跟踪算法重要的部分*/
    p-&gt;wts.demand = demand; /* 对应一个窗中运行的时间(根据window policy不同而有差异) */
    p-&gt;wts.demand_scaled = demand_scaled; /* 对应一个窗中运行的时间(根据window policy不同而有差异)缩放到1024 */ #############
    p-&gt;wts.coloc_demand = div64_u64(sum, sched_ravg_hist_size); /*5个窗口运行时间之和除以5，即5个窗口的平均运行时间*/
    p-&gt;wts.pred_demand = pred_demand;
    p-&gt;wts.pred_demand_scaled = pred_demand_scaled;

    /* demand_scaled 大于指定的阈值时，会做一些事情 */
    if (demand_scaled &gt; sysctl_sched_min_task_util_for_colocation) {
        p-&gt;wts.unfilter = sysctl_sched_task_unfilter_period; /*单位是ns，默认值是100ms*/
    } else {
        if (p-&gt;wts.unfilter)
            p-&gt;wts.unfilter = max_t(int, 0, p-&gt;wts.unfilter - rq-&gt;wrq.prev_window_size); //相当于衰减一个窗口的大小
    }

done:
    trace_sched_update_history(rq, p, runtime, samples, event);
}</code></pre></div><p>其中Trace:</p><p>trace_sched_update_history(rq, p, runtime, samples, event);</p><p>参数原型：</p><p>(struct rq *rq, struct task_struct *p, u32 runtime, int samples, enum task_event evt)</p><p>打印内容：</p><p>sched_update_history: 24647 (kworker/u16:15): runtime 279389 samples 1 event TASK_WAKE demand 717323<br />coloc_demand 717323 pred_demand 279389 (hist: 279389 88058 520130 1182596 1516443) cpu 1 nr_big 0</p><p>字段解析：</p><p>24647：取自 p-&gt;pid<br />kworker/u16:15：取自 p-&gt;comm<br />runtime：来自参数3，表示最近一个窗口中的运行时间，也是 p-&gt;wts.sum 的值<br />samples：来自参数4，表示更新几个窗的历史<br />event：取自 task_event_names[event]<br />demand：取自 p-&gt;wts.demand，是scale之前的根据不同window policy计算出来的负载值<br />coloc_demand：取自 p-&gt;wts.coloc_demand，即5个窗口的平均值<br />pred_demand：取自 p-&gt;wts.pred_demand，表示预测的负载需求<br />(hist: 279389 88058 520130 1182596 1516443)：取自 p-&gt;wts.sum_history[5]，是任务在最近5个窗口中分别运行的时间<br />cpu：取自 rq-&gt;cpu<br />nr_big：取自 rq-&gt;wrq.walt_stats.nr_big_tasks</p><p>用于预测任务的 demand 的 bucket 相关更新：</p><div class="codebox"><pre class="vscroll"><code>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-&gt;wts.busy_buckets 用于计算 */
    pred_demand = get_pred_busy(p, bidx, runtime);
    /* 更新 p-&gt;wts.busy_buckets */
    bucket_increase(p-&gt;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-&gt;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-&gt;wts.sum=8ms,那么传参就是(p, 5, 8)，*/
static u32 get_pred_busy(struct task_struct *p, int start, u32 runtime)
{
    int i;
    u8 *buckets = p-&gt;wts.busy_buckets; //10个元素
    u32 *hist = p-&gt;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 &lt; NUM_BUSY_BUCKETS; i++) {
        if (buckets[i]) {
            first = i;
            break;
        }
    }

    /* 若没找到桶下标，就直接返回 runtime，注意 runtime 可能大于10 */
    if (first &gt;= NUM_BUSY_BUCKETS)
        goto out;

    /* 计算用于预测的桶 */
    final = first;

    /* 确定预测桶的需求范围 */
    if (final &lt; 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 &lt; sched_ravg_hist_size; i++) {
        if (hist[i] &gt;= dmin &amp;&amp; hist[i] &lt; dmax) {
            ret = hist[i];
            break;
        }
    }
    /* no historical runtime within bucket found, use average of the bin 
     * 若找不到存储桶内的历史运行时间，就使用垃圾桶的平均值 */
    if (ret &lt; 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-&gt;wts.busy_buckets, bidx)*/
static inline void bucket_increase(u8 *buckets, int idx)
{
    int i, step;

    for (i = 0; i &lt; NUM_BUSY_BUCKETS; i++) { //10
        if (idx != i) { //不相等就衰减
            if (buckets[i] &gt; DEC_STEP) //2
                buckets[i] -= DEC_STEP; //2
            else
                buckets[i] = 0;
        } else { //相等
            step = buckets[i] &gt;= CONSISTENT_THRES ? INC_STEP_BIG : INC_STEP; //16 16 8
            if (buckets[i] &gt; U8_MAX - step) //255-step
                buckets[i] = U8_MAX; //255
            else
                buckets[i] += step; //就是加step，上面判断是为了不要溢出
        }
    }
}</code></pre></div><p>其中Trace:</p><p>trace_sched_update_pred_demand(p, runtime, mult_frac((unsigned int)cur_freq_runtime, 100,&#160; sched_ravg_window), ret);</p><p>参数原型：</p><p>(struct task_struct *p, u32 runtime, int pct, unsigned int pred_demand)</p><p>打印内容：</p><p>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)</p><p>字段解析：</p><p>1174：取自 p-&gt;pid<br />Binder:1061_2：取自 p-&gt;comm<br />runtime：取自参数2<br />pct：取自参数3<br />cpu：取自task_cpu(p)<br />pred_demand：取自参数4<br />(buckets: 0 255 0 0 0 0 0 0 0 0)：取自 p-&gt;wts.busy_buckets[10]</p><div class="codebox"><pre class="vscroll"><code>/* 
 * update_history --&gt; 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-&gt;wts.demand_scaled 约是由 p-&gt;wts.sum scale后得来的(window plicy策略影响), 后者是一个窗口中任务运行的时长。新的减旧的，结果处于[-1024,1024] */
    s64 task_load_delta = (s64)updated_demand_scaled - p-&gt;wts.demand_scaled;
    /* p-&gt;wts.pred_demand_scaled 是由桶算法预测得来的 */
    s64 pred_demand_delta = (s64)updated_pred_demand_scaled - p-&gt;wts.pred_demand_scaled;

    /* 直接加上传入的增量，注意增量可能是负数，一个进程的负载变低了，差值就是负数了*/
    fixup_cumulative_runnable_avg(&amp;rq-&gt;wrq.walt_stats, task_load_delta, pred_demand_delta);
    /*累加demand_scaled的增量*/
    walt_fixup_cum_window_demand(rq, task_load_delta); /*上下两个函数都是对rq-&gt;wrq.中的成员赋值*/
}

/*
 * 如果task在rq上调用路径：update_history --&gt; fixup_cumulative_runnable_avg 传参：(&amp;rq-&gt;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-&gt;cumulative_runnable_avg_scaled += demand_scaled_delta;
    BUG_ON((s64)stats-&gt;cumulative_runnable_avg_scaled &lt; 0);

    stats-&gt;pred_demands_sum_scaled += pred_demand_scaled_delta;
    BUG_ON((s64)stats-&gt;pred_demands_sum_scaled &lt; 0);
}</code></pre></div><p>说明 rq-&gt;wrq.walt_stats.cumulative_runnable_avg_scaled 和 rq-&gt;wrq.walt_stats.pred_demands_sum_scaled 统计的只是 runnable 状态的负载值。这里加上有符号的delta值，可以快速的反应runnable状态的负载的变化。</p><div class="codebox"><pre><code>/* 
 * 如果task在rq上调用路径：update_history --&gt; fixup_walt_sched_stats_common --&gt; this 传参：(rq, task_load_delta)
 * 如果rq-&gt;curr == p 时调用路径：update_history --&gt; this 传参：(rq, demand_scaled)
 * 说明这里面更新的成员统计的级包括 runnable 的分量，也保留 running 的分量
 */
static inline void walt_fixup_cum_window_demand(struct rq *rq, s64 scaled_delta)
{
    rq-&gt;wrq.cum_window_demand_scaled += scaled_delta;

    if (unlikely((s64)rq-&gt;wrq.cum_window_demand_scaled &lt; 0))
        rq-&gt;wrq.cum_window_demand_scaled = 0;
}</code></pre></div><p>rq-&gt;wrq.cum_window_demand_scaled 统计的既包括 runnable 的又包括 running 的。runnable 的累加的是差值，而 running 的累加的直接是 demand_scaled 的值，若是一部分 runnable 的任务变成 running 了，前者减少，后者增加，体现在结果上可能是不变的。</p><p>7. update_task_demand 函数</p><div class="codebox"><pre class="vscroll"><code>/*
 * 计算任务的cpu需求和/或更新任务的cpu需求历史
 *
 * ms = p-&gt;wts.mark_start
 * wc = wallclock
 * ws = rq-&gt;wrq.window_start
 *
 * 三种可能：
 *  a) 任务事件包含在一个窗口中。 16ms per-window, window_start &lt; mark_start &lt; wallclock
 *       ws    ms    wc
 *       |    |    |
 *       V    V    V
 *       |---------------|
 *
 * 在这种情况下，如果事件是合适的 p-&gt;wts.sum 被更新（例如：event == PUT_PREV_TASK）
 *
 * b) 任务事件跨越两个窗口。mark_start &lt; window_start &lt; wallclock
 *
 *       ms    ws     wc
 *       |    |     |
 *       V    V     V
 *      ------|-------------------
 *
 * 在这种情况下，如果事件是合适的 p-&gt;wts.sum 更新为 (ws - ms) ，然后记录一个新的窗口的采样，如果事件是合
 * 适的然后将 p-&gt;wts.sum 设置为 (wc - ws) 。
 *
 * c) 任务事件跨越两个以上的窗口。
 *
 *        ms ws_tmp                   ws  wc
 *        |  |                       |   |
 *        V  V                       V   V
 *        ---|-------|-------|-------|-------|------
 *           |                   |
 *           |&lt;------ nr_full_windows ------&gt;|
 *
 * 在这种情况下，如果事件是合适的，首先 p-&gt;wts.sum 更新为 (ws_tmp - ms) ，p-&gt;wts.sum 被记录，然后，如果
 * event 是合适的 window_size 的 &#039;nr_full_window&#039; 样本也被记录，最后如果 event 是合适的，p-&gt;wts.sum 更新
 * 到 (wc - ws)。
 *
 * 重要提示：保持 p-&gt;wts.mark_start 不变，因为 update_cpu_busy_time() 依赖它！
 *
 */
/* walt_update_task_ravg--&gt;this 唯一调用位置 */
static u64 update_task_demand(struct task_struct *p, struct rq *rq, int event, u64 wallclock) //walt.c
{
    u64 mark_start = p-&gt;wts.mark_start; //进来时还没更新
    u64 delta, window_start = rq-&gt;wrq.window_start; //进来时已经更新了
    int new_window, nr_full_windows;
    u32 window_size = sched_ravg_window; //20ms
    u64 runtime;

    new_window = mark_start &lt; window_start; //若为真说明经历了新窗口
    /* 若判断不需要更新负载，直接更新历史 p-&gt;wts.sum_history[]，而没有更新 p-&gt;wts.sum */
    if (!account_busy_for_task_demand(rq, p, event)) {
        if (new_window) {
            /*
             * 如果计入的时间没有计入繁忙时间，并且新的窗口开始，
             * 则只需要关闭前一个窗口与预先存在的需求。 多个窗口
             * 可能已经过去，但由于空窗口被丢弃，因此没有必要考虑这些。
             *
             * 如果被累积的时间没有被计入繁忙时间，并且有新的窗口开始，
             * 则只需要与预先存在需求的前一个窗口被关闭。 虽然可能有多
             * 个窗口已经流逝了，但由于WALT算法是空窗口会被丢弃掉，因
             * 此没有必要考虑这些。
             */
            update_history(rq, p, p-&gt;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&#039;s demand history */
    /* 将最开始的不足一个window窗口大小的delta计算出来的p-&gt;wts.sum放入历史数组中 */
    update_history(rq, p, p-&gt;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 累积运行时间进行累加 ==&gt;只要搞清什么时候标记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;
}</code></pre></div><p>此函数中始终没有更新回去 p-&gt;wts.mark_start，其是在 walt_update_task_ravg 函数最后更新的。rq-&gt;wrq.window_start 在上面第一个函数中就更新了。</p><div class="codebox"><pre class="vscroll"><code>/* update_task_demand --&gt; 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 &amp;&amp; (event == PICK_NEXT_TASK || event == TASK_MIGRATE)))
        return 0;

    /* (3) idle进程退出的时候也不需要统计 */
    if (event == PICK_NEXT_TASK &amp;&amp; rq-&gt;curr == rq-&gt;idle)
        return 0;

    /*
     * TASK_UPDATE can be called on sleeping task, when its moved between related groups
     */
    /*context_switch()的时候更改的rq-&gt;curr*/
    /* (4) 若是update事件，且p是curr任务，需要更新。否则若p在队列上需要更新，不在队列上不需要更新 */
    if (event == TASK_UPDATE) {
        if (rq-&gt;curr == p)
            return 1;

        return p-&gt;on_rq ? SCHED_ACCOUNT_WAIT_TIME : 0; //这里可调整是否记录任务在rq上的等待的时间
    }

    /* (5) 都不满足，默认是需要更新 */
    return 1;
}</code></pre></div><p>p是idle task，或 事件是 TASK_WAKE，或idle任务退出时的 PICK_NEXT_TASK 事件，或事件是 TASK_UPDATE 但是 p 不是curr任务也没有在rq上，就不需要计算busy time。只有事件是 TASK_UPDATE，且任务p是 rq-&gt;curr 任务或者 p是在rq 上等待，则需要更新。若不需要更新的话，又产生了新的窗口，那就调用 update_history()更新负载历史就退出了。</p><div class="codebox"><pre><code>/* update_task_demand --&gt; 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-&gt;wrq.task_exec_scale) &gt;&gt; 10, 由于 rq-&gt;wrq.task_exec_scale 初始化为1024，所以还是delta*/
    delta = scale_exec_time(delta, rq);
    /* 这里更新了 p-&gt;wts.sum，并将最大值钳位在一个窗口大小*/
    p-&gt;wts.sum += delta;
    if (unlikely(p-&gt;wts.sum &gt; sched_ravg_window))
        p-&gt;wts.sum = sched_ravg_window;

    return delta;
}</code></pre></div><p>更新 p-&gt;wts.sum 值，并且返回 delta 值。这也是 sum 的唯一更新位置，唯一调用路径也是从 walt_update_task_ravg 函数调用下来的。</p><p>8. update_cpu_busy_time 函数</p><div class="codebox"><pre class="vscroll"><code>/* walt_update_task_ravg --&gt; 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-&gt;curr);
    u64 mark_start = p-&gt;wts.mark_start;
    u64 window_start = rq-&gt;wrq.window_start; //walt_update_task_ravg--&gt;update_window_start 最先更新的rq-&gt;wrq.window_start
    u32 window_size = rq-&gt;wrq.prev_window_size;
    u64 delta;
    u64 *curr_runnable_sum = &amp;rq-&gt;wrq.curr_runnable_sum;
    u64 *prev_runnable_sum = &amp;rq-&gt;wrq.prev_runnable_sum;
    u64 *nt_curr_runnable_sum = &amp;rq-&gt;wrq.nt_curr_runnable_sum;
    u64 *nt_prev_runnable_sum = &amp;rq-&gt;wrq.nt_prev_runnable_sum;
    bool new_task;
    struct walt_related_thread_group *grp;
    int cpu = rq-&gt;cpu;
    u32 old_curr_window = p-&gt;wts.curr_window;

    new_window = mark_start &lt; window_start;
    if (new_window)
        full_window = (window_start - mark_start) &gt;= window_size;

    /* 处理每个任务的窗口翻转。 我们不关心空闲任务。*/
    if (!is_idle_task(p)) {
        if (new_window)
            /* 将 p-&gt;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 &amp;&amp; new_window) {
        /* rq的一些成员，prev_*_sum=curr_*_sum, 然后将 curr_*_sum 赋值为0 */
        rollover_cpu_window(rq, full_window);
        rollover_top_tasks(rq, full_window); //这里面已经更新了rq-&gt;wrq.curr_table ############
    }

    /* 判断是否需要记录 */
    if (!account_busy_for_cpu_time(rq, p, irqtime, event))
        goto done;
    /*----下面就是需要计算的了----*/

    grp = p-&gt;wts.grp;
    if (grp) {
        struct group_cpu_time *cpu_time = &amp;rq-&gt;wrq.grp_time;
        /* 注意：指向更改了! */
        curr_runnable_sum = &amp;cpu_time-&gt;curr_runnable_sum;
        prev_runnable_sum = &amp;cpu_time-&gt;prev_runnable_sum;

        nt_curr_runnable_sum = &amp;cpu_time-&gt;nt_curr_runnable_sum;
        nt_prev_runnable_sum = &amp;cpu_time-&gt;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-&gt;wts.curr_window += delta;
            p-&gt;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-&gt;wts.prev_window += delta;
            p-&gt;wts.prev_window_cpu[cpu] += delta;
        } else {
            /* 由于至少一个完整的窗口已经过去，对前一个窗口的贡献是一个完整的窗口(window_size) */
            delta = scale_exec_time(window_size, rq);
            p-&gt;wts.prev_window = delta;
            p-&gt;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-&gt;wts.curr_window = delta; /*对当前窗的贡献直接复制给当前窗*/
        p-&gt;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-&gt;wts.prev_window += delta;
                p-&gt;wts.prev_window_cpu[cpu] += delta;
            }
        } else {
            /* 由于至少一个完整的窗口已经过去，对前一个窗口的贡献是完整的窗口（window_size）*/
            delta = scale_exec_time(window_size, rq);
            if (!is_idle_task(p)) {
                p-&gt;wts.prev_window = delta;
                p-&gt;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-&gt;wts.curr_window = delta;
            p-&gt;wts.curr_window_cpu[cpu] = delta;
        }

        goto done;
    }
    /*---- 下面就对应 irqtime &amp;&amp; is_idle_task(p) &amp;&amp; ！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 &gt; window_start) {
            *curr_runnable_sum = scale_exec_time(irqtime, rq); //等效于直接返回irqtime，因为是idle线程，之前应该是0的
            return;
        }
        /*---下面是ms&lt;=ws---*/

        /*
         * IRQ 繁忙时间跨越多个窗口。 先处理当前窗口开始前的忙时间。
         */
        delta = window_start - mark_start;
        if (delta &gt; 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-&gt;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);
}</code></pre></div><p>值更新当前窗口和前一个窗口的busy时间，主要用于更新任务的： p-&gt;wts.curr_window、p-&gt;wts.curr_window_cpu[cpu]，更新rq 的 rq-&gt;wrq.curr_runnable_sum、rq-&gt;wrq.prev_runnable_sum，若是一个walt认为的新任务，还更新 rq-&gt;wrq.nt_curr_runnable_sum、rq-&gt;wrq.nt_prev_runnable_sum。然后是更新 top-task 的一些成员</p><p>下面分别是对 task、cpu、top_tasks 维护的 window 进行更新。有一个新的窗口到来时更新，若更新时已经经历了一个或多个完整的window，那么对prev和curr window 相关的描述结构进行清理备用。</p><div class="codebox"><pre><code>static u32 empty_windows[NR_CPUS];
/* 将 p-&gt;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-&gt;wts.curr_window;
        curr_cpu_windows = p-&gt;wts.curr_window_cpu;
    }

    p-&gt;wts.prev_window = curr_window;
    p-&gt;wts.curr_window = 0;

    /* Roll over individual CPU contributions 滚动每个 CPU 的贡献 */
    for (i = 0; i &lt; nr_cpu_ids; i++) {
        p-&gt;wts.prev_window_cpu[i] = curr_cpu_windows[i];
        p-&gt;wts.curr_window_cpu[i] = 0;
    }

    if (is_new_task(p))
        p-&gt;wts.active_time += task_rq(p)-&gt;wrq.prev_window_size; //active_time 的唯一更新位置, walt认为的新任务
}</code></pre></div><p>清理的是任务的 p-&gt;wts.prev_window_cpu、p-&gt;wts.curr_window、p-&gt;wts.prev_window_cpu[]、p-&gt;wts.curr_window_cpu[]</p><div class="codebox"><pre><code>/*
 * 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-&gt;wrq.curr_runnable_sum;
    u64 nt_curr_sum = rq-&gt;wrq.nt_curr_runnable_sum;
    u64 grp_curr_sum = rq-&gt;wrq.grp_time.curr_runnable_sum;
    u64 grp_nt_curr_sum = rq-&gt;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-&gt;wrq.prev_runnable_sum = curr_sum;
    rq-&gt;wrq.nt_prev_runnable_sum = nt_curr_sum;
    rq-&gt;wrq.grp_time.prev_runnable_sum = grp_curr_sum;
    rq-&gt;wrq.grp_time.nt_prev_runnable_sum = grp_nt_curr_sum;

    rq-&gt;wrq.curr_runnable_sum = 0;
    rq-&gt;wrq.nt_curr_runnable_sum = 0;
    rq-&gt;wrq.grp_time.curr_runnable_sum = 0;
    rq-&gt;wrq.grp_time.nt_curr_runnable_sum = 0;
}</code></pre></div><p>清理的是 rq-&gt;wrq 的 和 rq-&gt;wrq.grp_time 的 prev_runnable_sum、curr_runnable_sum、nt_prev_runnable_sum、nt_curr_runnable_sum</p><div class="codebox"><pre><code>static void rollover_top_tasks(struct rq *rq, bool full_window)
{
    /* 跟踪的是2个，构成一个环形数组 */
    u8 curr_table = rq-&gt;wrq.curr_table;
    u8 prev_table = 1 - curr_table;
    int curr_top = rq-&gt;wrq.curr_top;

    /*将prev window的数据结构清理后待用*/
    clear_top_tasks_table(rq-&gt;wrq.top_tasks[prev_table]); //memset(arg, 0, NUM_LOAD_INDICES * sizeof(u8));
    clear_top_tasks_bitmap(rq-&gt;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-&gt;wrq.top_tasks[curr_table]);
        clear_top_tasks_bitmap(rq-&gt;wrq.top_tasks_bitmap[curr_table]);
    }

    /*两个window的下标进行翻转，curr--&gt;prev,prev--&gt;curr*/
    rq-&gt;wrq.curr_table = prev_table;
    rq-&gt;wrq.prev_top = curr_top;
    rq-&gt;wrq.curr_top = 0;
}</code></pre></div><p>清理的是 rq-&gt;wrq 的 top_task 相关的成员。</p><p>然后调用 account_busy_for_cpu_time 判断清理后任务的和cpu的是否还需要更新上去</p><div class="codebox"><pre class="vscroll"><code>/* update_cpu_busy_time--&gt;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 &amp;&amp; TASK_MIGRATE is not possible on idle task!  idle task不可能出现唤醒和迁移 */
        if (event == PICK_NEXT_TASK)
            return 0;

        /* PUT_PREV_TASK, TASK_UPDATE &amp;&amp; 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-&gt;curr == p)
            return 1;

        return p-&gt;on_rq ? SCHED_FREQ_ACCOUNT_WAIT_TIME : 0; //在rq上和或正在迁移是1，但是冒号前后都是0
    }

    /* TASK_MIGRATE, PICK_NEXT_TASK left */
    return SCHED_FREQ_ACCOUNT_WAIT_TIME; //0
}</code></pre></div><p>top_task 维护的窗口更新：</p><div class="codebox"><pre class="vscroll"><code>/* 
 * update_cpu_busy_time--&gt;this 若p不是idle任务，就调用，传参(p, rq, old_curr_window, new_window, full_window) 
 * @ old_curr_window：取自 p-&gt;wts.curr_window，表示p在窗口翻转前在当前窗口的运行时间
 * @ new_window：bool值，若ms&lt;ws为真
 * @ full_window：bool值，若ws-ms&gt;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-&gt;wrq.curr_table;
    u8 prev = 1 - curr;
    u8 *curr_table = rq-&gt;wrq.top_tasks[curr];
    u8 *prev_table = rq-&gt;wrq.top_tasks[prev];
    int old_index, new_index, update_index;
    u32 curr_window = p-&gt;wts.curr_window;
    u32 prev_window = p-&gt;wts.prev_window;
    bool zero_index_update;

    /* 两个窗的运行时间相等或新窗口还没有到来 */
    if (old_curr_window == curr_window &amp;&amp; !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 &amp;&amp; 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 &gt; rq-&gt;wrq.curr_top)
                rq-&gt;wrq.curr_top = new_index; //更新rq-&gt;wrq.curr_top成员
        }

        if (!curr_table[old_index])
            __clear_bit(NUM_LOAD_INDICES - old_index - 1, rq-&gt;wrq.top_tasks_bitmap[curr]); //这个bit数组表示此运行时间下有没有计数值

        if (curr_table[new_index] == 1)
            __set_bit(NUM_LOAD_INDICES - new_index - 1, rq-&gt;wrq.top_tasks_bitmap[curr]);

        return;
    }
    /*---下面是new_window!=0的情况了---*/

    /*
     * 对于此任务来说窗口已经翻转。 当我们到达这里时，curr/prev 交换已经发生。 
     * 所以我们需要对新索引使用 prev_window 。
     */
    update_index = load_to_index(prev_window);

    if (full_window) { //至少有一个满窗
        /*
         * 这里有两个案例。 要么&#039;p&#039; 运行了整个窗口，要么根本不运行。 在任何一种情况下，
         * prev 表中都没有条目。 如果 &#039;p&#039; 运行整个窗口，我们只需要在 prev 表中创建一个
         * 新条目。 在这种情况下，update_index 将对应于 sched_ravg_window，因此我们可
         * 以无条件地更新顶部索引。
         */
        if (prev_window) {
            prev_table[update_index] += 1;
            rq-&gt;wrq.prev_top = update_index;
        }

        if (prev_table[update_index] == 1)
            __set_bit(NUM_LOAD_INDICES - update_index - 1, rq-&gt;wrq.top_tasks_bitmap[prev]);
    } else { //产生了新窗口，但是还没达到一个满窗
        zero_index_update = !old_curr_window &amp;&amp; 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 &gt; rq-&gt;wrq.prev_top)
                rq-&gt;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-&gt;wrq.top_tasks_bitmap[prev]);
            if (prev_table[update_index] == 1)
                __set_bit(NUM_LOAD_INDICES - update_index - 1, rq-&gt;wrq.top_tasks_bitmap[prev]);
        }
    }

    if (curr_window) {
        curr_table[new_index] += 1;

        if (new_index &gt; rq-&gt;wrq.curr_top)
            rq-&gt;wrq.curr_top = new_index;

        if (curr_table[new_index] == 1)
            __set_bit(NUM_LOAD_INDICES - new_index - 1, rq-&gt;wrq.top_tasks_bitmap[curr]);
    }
}</code></pre></div><p>top_tasks 的维护中也使用到了桶，新窗运行时间对应的 curr_table[]成员加1，之前窗口运行时间对应的 prev_table[] 成员减1。</p><p>9. update_task_pred_demand 函数</p><div class="codebox"><pre class="vscroll"><code>/*
 * 在窗口翻转时计算任务的预测需求。如果任务当前窗口繁忙时间超过预测需求，则在此处更新以反映任务需求。
 */
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 &amp;&amp; event != TASK_UPDATE &amp;&amp;
            (!SCHED_FREQ_ACCOUNT_WAIT_TIME || (event != TASK_MIGRATE &amp;&amp; event != PICK_NEXT_TASK)))
        return;

    /*
     * 当它在相关组之间移动时，TASK_UPDATE 可以在睡眠任务上调用。
     */
    if (event == TASK_UPDATE) {
        if (!p-&gt;on_rq &amp;&amp; !SCHED_FREQ_ACCOUNT_WAIT_TIME)
            return;
    }

    new = calc_pred_demand(p);
    old = p-&gt;wts.pred_demand;

    if (old &gt;= new)
        return;
    /*---下面就是 new &gt; old 的情况---*/

    new_scaled = scale_demand(new); //new/window_size*1024
    /* p是on rq的状态并且不是已经被throttle的deadline任务 */
    if (task_on_rq_queued(p) &amp;&amp; (!task_has_dl_policy(p) || !p-&gt;dl.dl_throttled))
        fixup_walt_sched_stats_common(rq, p, p-&gt;wts.demand_scaled, new_scaled);

    p-&gt;wts.pred_demand = new;
    p-&gt;wts.pred_demand_scaled = new_scaled;
}</code></pre></div><p>注意，这里再次调用了 fixup_walt_sched_stats_common，在 walt_update_task_ravg 函数中，在 update_history 中已经调用过一次，进入条件也相同，也是p在队列上。</p><div class="codebox"><pre><code>static inline u32 calc_pred_demand(struct task_struct *p)
{
    /* 预测的需求比当前窗口的大，就返回预测的需求 */
    if (p-&gt;wts.pred_demand &gt;= p-&gt;wts.curr_window)
        return p-&gt;wts.pred_demand;

    return get_pred_busy(p, busy_to_bucket(p-&gt;wts.curr_window), p-&gt;wts.curr_window);
}</code></pre></div><p>get_pred_busy 和 busy_to_bucket 两个函数上面都有列出。</p><p>10. run_walt_irq_work 函数</p><div class="codebox"><pre><code>static inline void run_walt_irq_work(u64 old_window_start, struct rq *rq) //walt.c
{
    u64 result;

    /*若是还是同一个窗，直接退出*/
    if (old_window_start == rq-&gt;wrq.window_start)
        return;

    /* 
     * atomic64_cmpxchg(*ptr, old, new) 函数功能是：将old和ptr指向的内容比较，如果相等，
     * 则将new写入到ptr指向的内存中，并返回old，如果不相等，则返回ptr指向的内容。
     */
    result = atomic64_cmpxchg(&amp;walt_irq_work_lastq_ws, old_window_start, rq-&gt;wrq.window_start);
    if (result == old_window_start) {
        walt_irq_work_queue(&amp;walt_cpufreq_irq_work); //触发回调 walt_irq_work()，构成一个&quot;内核线程&quot;，循环往复执行

        trace_walt_window_rollover(rq-&gt;wrq.window_start);
    }
}</code></pre></div><p>walt_irq_work_queue 会触发 walt_irq_work() 被调用，这个函数中又会调用 walt_update_task_ravg，walt_update_task_ravg 函数会在每个tick中调用，这里这样实现可能是针对没有tick的场景使用。</p><p>其中Trace:</p><p>trace_walt_window_rollover(rq-&gt;wrq.window_start);</p><p>参数原型：</p><p>(u64 window_start)</p><p>打印内容：</p><p>//20ms间隔执行一次<br />&lt;idle&gt;-0&#160; &#160; &#160;[002] d..2 48262.320451: walt_window_rollover: window_start=48262548000001<br />&lt;idle&gt;-0&#160; &#160; &#160;[001] d.h2 48262.340457: walt_window_rollover: window_start=48262568000001</p><p>字段解析：</p><p>window_start 就是打印 rq-&gt;wrq.window_start 的记录的时间值，单位是ns.</p><p>四、总结</p><p>&#160; &#160; WALT负载计算算法是基于窗口的，对window有一个rollover的操作，只跟踪curr和prev两个窗口，curr窗口的下标由 wrq.curr_table 指向，两个窗口构成一个唤醒缓冲区，prev和curr进行不断切换。<br />&#160; &#160; walt_update_task_ravg 函数通过其 event 成员决定对哪些事件计算负载，再根据其调用路径和其调用函数对是否是在rq上，是否是p=rq-&gt;curr可以判断统计的是哪部分的负载。<br />&#160; &#160; 预测负载这块，对于任务和CPU都使用了桶，任务是10个桶，对于cpu的curr和prev两个窗口分别是1000个成员，命中累加，不命中衰减。<br />&#160; &#160; walt_update_task_ravg 在tick的调用路径中有调用，应该是为了无tick情况下walt仍然能正常工作，使用irq_work构成一个内核线程以一个窗口的周期来更新窗口。</p><p>五、补充</p><p>&#160; &#160; task util的获取：task_util() WALT: p-&gt;wts.demand_scaled；PELT: p-&gt;se.avg.util_avg<br />&#160; &#160; cpu util的获取：cpu_util_cum() WALT: rq-&gt;wrq.cum_window_demand_scaled；PELT: rq-&gt;cfs.avg.util_avg<br />&#160; &#160; task_util() 函数</p><br /><div class="codebox"><pre><code>static inline unsigned long task_util(struct task_struct *p)
{
#ifdef CONFIG_SCHED_WALT
    if (likely(!walt_disabled &amp;&amp; sysctl_sched_use_walt_task_util))
        return p-&gt;wts.demand_scaled;
#endif
    return READ_ONCE(p-&gt;se.avg.util_avg);
}</code></pre></div>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Wed, 27 Mar 2024 13:16:17 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=964#p964</guid>
		</item>
	</channel>
</rss>
