調度器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);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM