調度器12—PELT算法中的預估利用率 util_est


基於MTK Linux-4.14

 

一、PELT 中預估利用率簡介

由於在 PELT 算法下任務的 util 增加減少的都比較慢,對於長時間休眠后的重負載任務,其 util 增加的比較慢,導致不能及時觸發提頻和遷核。為了補救 PELT 的這一缺陷,引入了預估負載。在任務(休眠)出隊列時更新任務的預估負載,當任務入隊列時將出隊列時的負載加到cfs_rq的預估負載上。

 

二、PELT 中預估利用率的賦值和使用

1. 賦值

(1) 調度實體 dequeue 時

static void util_est_dequeue(struct cfs_rq *cfs_rq, struct task_struct *p, bool task_sleep) //fair.c
{
    if (!sched_feat(UTIL_EST))
        return;

    ue.enqueued = 0; //若是沒有任務,默認就是0了
    if (cfs_rq->nr_running) {
        ue.enqueued  = cfs_rq->avg.util_est.enqueued;
        //保證不要被減成負數了,enqueue任務是或上,然后cfs_se_util_change/util_est_dequeue中去除
        ue.enqueued -= min_t(unsigned int, ue.enqueued, (_task_util_est(p) | UTIL_AVG_UNCHANGED));
    }
    WRITE_ONCE(cfs_rq->avg.util_est.enqueued, ue.enqueued); //dequeue時將任務的util_est從rq的util_est中移除
    trace_sched_util_est_cpu(cpu_of(rq_of(cfs_rq)), cfs_rq);

  if (!task_sleep) //只有因為睡眠而觸發的任務切換,task_sleep判斷才是真。也就是說只有是任務睡眠才更新,被強占不更新。
    return;

    ...
    /* 如果 PELT 值自入隊時間以來未更改,請跳過 util_est 更新 */
    ue = p->se.avg.util_est; //對ue做了賦值更改,此時是任務的了。
    if (ue.enqueued & UTIL_AVG_UNCHANGED) //enqueue任務是或上,然后cfs_se_util_change/util_est_dequeue中去除
        return;

    /*當任務的 EWMA 已經接近其上次激活值約 1% 時,跳過更新任務的估計利用率 */
    ue.enqueued = (task_util(p) | UTIL_AVG_UNCHANGED); //p->se.avg.util_avg,enqueued 成員賦值的是此次睡眠時任務的實際 util
    last_ewma_diff = ue.enqueued - ue.ewma;
    if (within_margin(last_ewma_diff, (SCHED_CAPACITY_SCALE / 100)))
        return;

    /* ue.ewma的計算公式:ewma(t) = w * task_util(p) + (1-w) * ewma(t-1) ==> 整理后:w * (last_ewma_diff + ewma(t-1) / w),w 取1/4 */
    ue.ewma <<= UTIL_EST_WEIGHT_SHIFT;
    ue.ewma  += last_ewma_diff;
    ue.ewma >>= UTIL_EST_WEIGHT_SHIFT;
    WRITE_ONCE(p->se.avg.util_est, ue); //寫回給任務,也就是說任務在出隊列時更新其 p->se.avg.util_est.enqueued 和 ewma

    trace_sched_util_est_task(p, &p->se.avg);
}

先將此任務的 util_est 從 cfs_rq 的 util_est 刪除,然后再更新任務的 util_est。

調用路徑:

dequeue_task_fair //fair.c,dequeue的是task不是entity,最后調用
    util_est_dequeue

當任務 dequeue 時,先將其 ue.enqueued 從 cfs_rq->avg.util_est.enqueued 中移除,然后再更新任務的 util_est。也就是說 cfs_rq->avg.util_est.enqueued 只是enqueue到rq上的任務的util_est之和(runnable+runing).

 

(2) 調度實體 enqueue 時

static inline void util_est_enqueue(struct cfs_rq *cfs_rq, struct task_struct *p) //fair.c
{
    if (!sched_feat(UTIL_EST))
        return;

    /* Update root cfs_rq's estimated utilization */
    enqueued  = cfs_rq->avg.util_est.enqueued;
    enqueued += (_task_util_est(p) | UTIL_AVG_UNCHANGED); //enqueue任務是或上,然后cfs_se_util_change中去除
    WRITE_ONCE(cfs_rq->avg.util_est.enqueued, enqueued);

    trace_sched_util_est_task(p, &p->se.avg);
    trace_sched_util_est_cpu(cpu_of(rq_of(cfs_rq)), cfs_rq);
}

調用路徑:

enqueue_task_fair //fair.c 最先調用的
    util_est_enqueue

當調度實體 enqueue 時,直接將其 util_est 加到 cfs_rq 的 util_est 中。

 

(3) util_change 中清除標志位

static inline void cfs_se_util_change(struct sched_avg *avg)
{
    if (!sched_feat(UTIL_EST))
        return;

    /* Avoid store if the flag has been already set */
    enqueued = avg->util_est.enqueued;
    if (!(enqueued & UTIL_AVG_UNCHANGED)) //沒有這個標志位直接退出
        return;

    /* Reset flag to report util_avg has been updated */
    enqueued &= ~UTIL_AVG_UNCHANGED; //從數值中清除這個標志位
    WRITE_ONCE(avg->util_est.enqueued, enqueued);
}

調用路徑:

enqueue_task_fair
unthrottle_cfs_rq
    enqueue_entity //fair.c 無條件條調用
dequeue_task_fair
throttle_cfs_rq
    dequeue_entity //fair.c 無條件條調用
    set_next_entity //fair.c 只有se->on_rq才調用,選一個cfs任務去運行
    put_prev_entity //fair.c 只有se->on_rq才調用,取消一個在運行的cfs任務
    entity_tick //fair.c 在scheduler_tick()中對正在運行的任務以Hz為頻度更新負載
    enqueue_task_fair //fair.c 無條件調用
    dequeue_task_fair //fair.c 無條件調用
    update_blocked_averages //fair.c 負載均衡路徑
    propagate_entity_cfs_rq //fair.c fair group sched里面的
    detach_entity_cfs_rq //fair.c 無條件條調用
    attach_entity_cfs_rq //fair.c 無條件條調用
    sched_group_set_shares //fair.c fair group sched cgroup分組
        update_load_avg //fair.c
            __update_load_avg_se //fair.c
                cfs_se_util_change(&se->avg) //傳參是se的sched_avg

這個函數只是將 UTIL_AVG_UNCHANGED 標志位清除掉,這個標志位是 enqueue_entity 時或上的,在 dequeue_task_fair 和 cfs_se_util_change 中清除。在 util_est_enqueue 中已經將任務的預估負載加到 cfs_rq 上了,在這之后的任何時刻應該都可以清除標志位。

 

2. 使用

(1) 任務的預估負載的獲取

static inline unsigned long _task_util_est(struct task_struct *p)
{
    struct util_est ue = READ_ONCE(p->se.avg.util_est);
    return max(ue.ewma, ue.enqueued); //兼顧實時性和休眠的歷史負載,取二者之間的較大值,rwma記錄了歷史衰減。
}

unsigned long task_util_est(struct task_struct *p)
{
    return max(task_util(p), _task_util_est(p));
}

調用路徑:

update_sg_util //eas_plus.c
find_energy_efficient_cpu_enhanced //eas_plus.c
schedtune_task_margin //fair.c
boosted_task_util //fair.c
find_best_target  //fair.c
get_eenv //fair.c
find_energy_efficient_cpu //fair.c
wake_energy
select_task_rq_fair //fair.c 這里對util_est進行trace
load_balance //fair.c
calc_cpu_util //sched_ctl.c
    task_util_est

這些路徑下都是直接獲取帶有預估的util數據。

 

(2) CPU的預估負載的獲取

static inline unsigned long cpu_util(int cpu)
{
    struct cfs_rq *cfs_rq;
    unsigned int util;

    /* WLAT算法下CPU的util */
#ifdef CONFIG_SCHED_WALT
    if (likely(!walt_disabled && sysctl_sched_use_walt_cpu_util)) {
        u64 walt_cpu_util = cpu_rq(cpu)->cumulative_runnable_avg; //walt算法下返回的是這個,是不使用預估負載的

        walt_cpu_util <<= SCHED_CAPACITY_SHIFT;
        do_div(walt_cpu_util, walt_ravg_window);

        return min_t(unsigned long, walt_cpu_util, capacity_orig_of(cpu));
    }
#endif

    /* PELT算法下CPU的util */
    cfs_rq = &cpu_rq(cpu)->cfs;
    util = READ_ONCE(cfs_rq->avg.util_avg);

    if (sched_feat(UTIL_EST))
        util = max(util, READ_ONCE(cfs_rq->avg.util_est.enqueued)); //取cfs_rq的util和預估util的較大值, 此處獲取cpu的util直接使用的是enqueued成員,更具有實時性。

    return min_t(unsigned long, util, capacity_orig_of(cpu));
}

調用路徑:

    show_eas_info_attr //eas_plus.c 接口/sys/devices/system/cpu/eas/info 打印的util= 241(on),on表示此cpu是否onlline
        show_cpu_capacity //eas_plus.c
            get_cpu_util //fair.c
    sugov_update_single/sugov_update_shared //cpufreq_schedutil.c 調頻接口函數
        sugov_get_util //cpufreq_schedutil.c 獲取的是這個util觸發調頻的
            boosted_cpu_util //fair.c stune還可以對一個cpu進行boost, TODO:看
                cpu_util_freq //fair.c
                cpu_util_without //fair.c 不包括任務p的cpu的util,通常是在任務遷移、喚醒選核時使用
find_energy_efficient_cpu //fair.c 選擇能效最高的cpu    
    select_energy_cpu_idx //fair.c
        compute_energy //fair.c
            calc_sg_energy //fair.c
                group_idle_state //fair.c 計算此sched_group中所有cpu的cpu_util只和
        enqueue_task_fair
        task_tick_fair
            update_overutilized_status //fair.c
    find_busiest_group //fair.c 負載均衡時查找最繁忙的組
        update_sd_lb_stats //fair.c
            update_sg_lb_stats //fair.c
        load_balance //fair.c
            need_active_balance //fair.c
        SELECT_TASK_RQ_FAIR //fair.c
            nohz_kick_needed //fair.c no_hz相關
            trigger_load_balance //fair.c
                cpu_overutilized //fair.c 主要是負載均衡和遷核功能中調用
                update_sg_lb_stats //fair.c 對一個組內的sg_lb_stats.group_util進行累加賦值
                    cpu_util(cpu)

觸發調頻使用的是 boosted_cpu_util() 獲取的cpu util,任務遷移和均衡使用的是 cpu_overutilized() 進行判斷的。

 


免責聲明!

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



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