• 调度器6—WALT负载计算


    一、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);
    }
  • 相关阅读:
    MySQL_02之增删改查、PHP数据库操作
    MySQL_01之MySQL数据库基础
    git SSH key生成步骤
    Angular路由的定义和使用
    angular ng-href小测试
    AngularJs 内置指令
    一些移动端浏览器的兼容性Bug
    angular之隐藏显示,CSS类和样式
    一个用于展示的网站
    git extensions stash和stash pop
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15376806.html
Copyright © 2020-2023  润新知