1. 程式人生 > 其它 >排程器22—CPU頻點設定函式分析

排程器22—CPU頻點設定函式分析

基於 Linux-5.10

一、概述

1. 調頻就是根據需求設定合理的頻點。主要思想是在util變化時設定頻點來及時改變算力,以滿足效能功耗的需求。調頻和限頻,在 sugov_update_shared/sugov_update_single 歸一。

//調頻:
cpufreq_update_util --> sugov_update_shared/sugov_update_single 進入調頻設定。
//限頻:
scaling_min_freq/scaling_max_freq 檔案 --> freq_qos_update_request() --> 設定 sg_policy->limits_changed/sg_policy->need_freq_update 標誌--> sugov_update_shared/sugov_update_single中立即更新

2. CFS、RT、DL 排程類中都有呼叫 cpufreq_update_util() 設定頻點。


二、調頻呼叫路徑

1. 核心中設定頻點的函式

static inline void cpufreq_update_util(struct rq *rq, unsigned int flags)
{
    struct update_util_data *data;

    data = rcu_dereference_sched(*per_cpu_ptr(&cpufreq_update_util_data, cpu_of(rq)));
    if (data)
        data
->func(data, rq_clock(rq), flags); }

fair.c中的呼叫路徑:

attach_entity_load_avg //傳參(cfs_rq, 0)
detach_entity_load_avg //傳參(cfs_rq, 0)
update_load_avg //傳參(cfs_rq, 0)
    cfs_rq_util_change(cfs_rq, flags) //fair.c
        cpufreq_update_util(rq, flags);

enqueue_task_fair //fair.c 若enqueue的任務 p->in_iowait 則呼叫
    cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT);

update_blocked_averages 
//fair.c 若是有負載衰減就呼叫 cpufreq_update_util(rq, 0);

除了 enqueue 的任務 p->in_iowait 呼叫時傳參 flags=SCHED_CPUFREQ_IOWAIT 外,其它呼叫傳的flags都是0

rt.c中的呼叫路徑:

sched_rt_runtime_exceeded //判斷rt throttled才會呼叫
    sched_rt_rq_dequeue
        cpufreq_update_util(rq, 0);

sched_rt_rq_enqueue
enqueue_rt_entity
dequeue_rt_entity
    enqueue_top_rt_rq //不使能RT組排程的話,這是此檔案唯一使用位置
        cpufreq_update_util(rq, 0);

deadline.c中的呼叫路徑:

task_contending
dl_task_offline_migration
enqueue_task_dl
    add_running_bw
        __add_running_bw
            cpufreq_update_util(rq, 0);

dl_change_utilization
task_non_contending
dl_task_offline_migration
inactive_task_timer
dequeue_task_dl
migrate_task_rq_dl
switched_from_dl
    sub_running_bw
        __sub_running_bw
            cpufreq_update_util(rq, 0);

三、多核cluster調頻函式——sugov_update_shared

static void sugov_update_shared(struct update_util_data *hook, u64 time, unsigned int flags)
{
    struct sugov_cpu *sg_cpu = container_of(hook, struct sugov_cpu, update_util);
    struct sugov_policy *sg_policy = sg_cpu->sg_policy;
    unsigned int next_f;

    raw_spin_lock(&sg_policy->update_lock);

    //處理 iowait_boost 邏輯
    sugov_iowait_boost(sg_cpu, time, flags);
    sg_cpu->last_update = time;

    ignore_dl_rate_limit(sg_cpu, sg_policy);

    /*
     * 返回true的條件:
     *    1.有pending的限頻設定; 
     *    2.距上次調頻時間超過檔案min(down_rate_limit_us,up_rate_limit_us)單位us
     */
    if (sugov_should_update_freq(sg_policy, time)) {
        //獲取要設定的頻點
        next_f = sugov_next_freq_shared(sg_cpu, time);

        if (sg_policy->policy->fast_switch_enabled)
            sugov_fast_switch(sg_policy, time, next_f); //設定頻點, next_f單位kHz
        else
            sugov_deferred_update(sg_policy, time, next_f);
    }
    ...

    raw_spin_unlock(&sg_policy->update_lock);
}

cpu個數大於1的cluster的調頻使用此函式,其主要邏輯為:

1. iowait_boost 處理邏輯

只對 enqueue_task_fair 中判斷enqueue的任務 p->in_iowait 呼叫的 cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT) 進行響應,限定此調頻路徑中util最小是 IOWAIT_BOOST_MIN。並且會根據後續不同iowait_boost調頻需求而不同。需要結合 sugov_iowait_boost() 和 sugov_iowait_apply() 一起看,若每次都能設定下去,分五種情況:

首次iowait boost:sg_cpu->iowait_boost 取 IOWAIT_BOOST_MIN,預設是128
(1) 4ms內再次iowait boost:sg_cpu->iowait_boost 變為原來的2倍,
(2) 4ms內不再iowait boost:sg_cpu->iowait_boost 衰減為原來的1/2,若小於IOWAIT_BOOST_MIN,設定為0
(3) 4ms後再次iowait boost:sg_cpu->iowait_boost,先被設定為 IOWAIT_BOOST_MIN,然後再變為之前的2倍,
(4) 4ms後不再iowait boost:sg_cpu->iowait_boost 設定為0。

由 sg_policy->min_rate_limit_ns 控制設定流程進入的頻次,每1ms最多隻能設定下去一次。

2. 判斷是否要 update_freq 的邏輯

主要是在 sugov_should_update_freq() 中判斷。判斷需要更新頻點的條件:
(1) 有pending的限頻設定, sg_policy->limits_changed 為true;
(2) 距上次調頻時間超過檔案 min(down_rate_limit_us,up_rate_limit_us) 設定的值,單位us。


3. fast_switch 的邏輯

主要由 sugov_fast_switch()函式來完成,先根據檔案 down_rate_limit_us、up_rate_limit_us 來確定此時是否能進行降低、提高頻點的設定,可以設定就呼叫cpufreq_driver_fast_switch()進行實際的設定。

//sugov_fast_switch --> cpufreq_driver_fast_switch:
unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy, unsigned int target_freq)
{
    unsigned int freq;
    int cpu;

    //最大頻點最小頻點限制,尊重使用者空間 scaling_max_freq、scaling_min_freq 檔案的設定。
    target_freq = clamp_val(target_freq, policy->min, policy->max);
    //將新頻點設定到硬體中。fast_switch 中沒有對target_freq的單位做轉換,說明其單位也是kHz
    freq = cpufreq_driver->fast_switch(policy, target_freq); mtk_cpufreq_hw_fast_switch
    if (!freq)
        return 0;

    policy->cur = freq;
    //新頻點與最大頻點對應的cap的比值乘以1024設定到此cluster所有cpu的per_cpu(freq_scale)中
    arch_set_freq_scale(policy->related_cpus, freq, policy->cpuinfo.max_freq);
    //更新 /sys/devices/system/cpu/cpuX/cpufreq/stats 下的頻點統計資訊
    cpufreq_stats_record_transition(policy, freq);

    trace_android_rvh_cpufreq_transition(policy);

    if (trace_cpu_frequency_enabled()) {
        for_each_cpu(cpu, policy->cpus)
            trace_cpu_frequency(freq, cpu); //實時顯示,單位kHz
    }

    return freq;
}

4. 設定中有兩個trace:

(1) trace_sugov_ext_util

//sugov_update_shared --> sugov_next_freq_shared --> trace_sugov_ext_util

<...>-1136    [001] d..3 243713.479557: sugov_ext_util: cpu=0 util=120 min=118 max=1024
//util為計算考慮 clamp的、rt的、irq的、DL的之後,與 iowait_boost 比,取較大值。
//min、max分別為 rq->uclamp[UCLAMP_MIN].value、rq->uclamp[UCLAMP_MAX].value

(2) trace_cpu_frequency

//sugov_fast_switch-->cpufreq_driver_fast_switch-->trace_cpu_frequency
//實時顯示,trace中頻點單位kHz
kworker/u16:3-22890   [004] d..5 262317.291198: cpu_frequency: state=1600000 cpu_id=4

四、單核cluster調頻函式——sugov_update_single

static void sugov_update_single(struct update_util_data *hook, u64 time, unsigned int flags)
{
    struct sugov_cpu *sg_cpu = container_of(hook, struct sugov_cpu, update_util);
    struct sugov_policy *sg_policy = sg_cpu->sg_policy;
    struct rq *rq;
    unsigned long umin, umax;
    unsigned long util, max;
    unsigned int next_f;
    bool busy;

    raw_spin_lock(&sg_policy->update_lock);

    //處理 iowait_boost 邏輯
    sugov_iowait_boost(sg_cpu, time, flags);
    sg_cpu->last_update = time;

    ignore_dl_rate_limit(sg_cpu, sg_policy);

    if (!sugov_should_update_freq(sg_policy, time)) {
        raw_spin_unlock(&sg_policy->update_lock);
        return;
    }
    ...

    /* Limits may have changed, don't skip frequency update */
    //沒有pending的限頻時設定,且idle計數沒有增加,就認為busy
    busy = !sg_policy->need_freq_update && sugov_cpu_is_busy(sg_cpu);

    util = sugov_get_util(sg_cpu); //util計算考慮 clamp的、rt的、irq的、DL的,考慮了uclamp
    max = sg_cpu->max;
    //util和iowait_boost值取max
    util = sugov_iowait_apply(sg_cpu, time, util, max);

    if (trace_sugov_ext_util_enabled()) {
        rq = cpu_rq(sg_cpu->cpu);

        umin = rq->uclamp[UCLAMP_MIN].value; //主要看這個,最終util取的min一定大於或等於它
        umax = rq->uclamp[UCLAMP_MAX].value;

        trace_sugov_ext_util(sg_cpu->cpu, util, umin, umax);
    }

    next_f = get_next_freq(sg_policy, util, max);
    /*
     * Do not reduce the frequency if the CPU has not been idle
     * recently, as the reduction is likely to be premature then.
     */
    if (busy && next_f < sg_policy->next_freq) {
        //直接取前一次的頻點,下面設定路徑中判斷相等會終止設定
        next_f = sg_policy->next_freq;

        /* Reset cached freq as next_freq has changed */
        sg_policy->cached_raw_freq = 0;
    }

    /*
     * This code runs under rq->lock for the target CPU, so it won't run
     * concurrently on two different CPUs for the same target and it is not
     * necessary to acquire the lock in the fast switch case.
     */
    if (sg_policy->policy->fast_switch_enabled) {
        sugov_fast_switch(sg_policy, time, next_f);
    } else {
        sugov_deferred_update(sg_policy, time, next_f);
    }

    raw_spin_unlock(&sg_policy->update_lock);
}

1. 只有一個cpu的cluster的調頻使用此函式,一般是大核。邏輯大體和shared的相同,不同之處有:

(1) 若沒有pending的限頻設定,且距離上次設定頻點此cpu的idle計數沒有增加,就認為busy,就會放棄降低頻點的設定,直到有限頻設定進來或CPU進入idle。