基於 Linux-5.10
一、概述
1. 調頻就是根據需求設置合理的頻點。主要思想是在util變化時設置頻點來及時改變算力,以滿足性能功耗的需求。調頻和限頻,在 sugov_update_shared/sugov_update_single(若使用的governor是cpufreq_schedutil.c) 歸一。
//調頻: 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); }
一般是在 cpufreq_governor 的 start 回調中調用這個函數注冊調頻對應的governor對應的回調函數:
void cpufreq_add_update_util_hook(int cpu, struct update_util_data *data, void (*func)(struct update_util_data *data, u64 time, unsigned int flags)) //kernel/sched/cpufreq.c { ... data->func = func; rcu_assign_pointer(per_cpu(cpufreq_update_util_data, cpu), data); } EXPORT_SYMBOL_GPL(cpufreq_add_update_util_hook);
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。
五、governor的切換
1. governor的相關回調
cpufreq_governor.start sugov_start for_each_cpu(cpu, policy->cpus) cpufreq_add_update_util_hook(cpu, &sg_cpu->update_util, policy_is_shared(policy) ? sugov_update_shared : sugov_update_single); data->func = func; rcu_assign_pointer(per_cpu(cpufreq_update_util_data, cpu), data); cpufreq_governor.stop sugov_stop for_each_cpu(cpu, policy->cpus) cpufreq_remove_update_util_hook(cpu); rcu_assign_pointer(per_cpu(cpufreq_update_util_data, cpu), NULL);
echo walt > cpufreq/policyX/scaling_governor 切換 governor 時,在core中執行:
store_scaling_governor cpufreq_set_policy cpufreq_stop_governor policy->governor->stop(policy); //調用舊governor的stop回調 cpufreq_exit_governor policy->governor->exit(policy); policy->governor = new_gov; //切為新governor cpufreq_init_governor policy->governor->init(policy); cpufreq_start_governor policy->governor->start(policy); //調用新governor的start回調
可以看到,原生調度器的調頻函數 cpufreq_update_util() 在 governor 切換時,會切為使用新 governor 提供的回調函數。
六、driver的注冊
定義並初始化一個 struct cpufreq_driver 結構,然后調用 cpufreq_register_driver() 去注冊它。此函數中會將注冊的driver賦值給一個全局的 cpufreq_driver 指針,之后訪問driver都通過這個指針進行。
只有唯一的指針指向,因此也只能有一個,而不能像governor那樣可以有多個供切換。
cpufreq_register_driver //drivers/cpufreq/cpufreq.c cpufreq_driver = driver_data; /sys/devices/system/cpu/cpufreq/policy0 # ls -l ... -r--r--r-- 1 root root 4096 2022-01-01 03:50 scaling_driver //只讀,看driver是哪個 -rw-r--r-- 1 root root 4096 2022-01-01 03:59 scaling_governor //可設置
七、Qcom的walt governor
Qcom最新主流平台使用的是自己的walt governor,調度器也並不是使用 cpufreq_update_util() 進行調頻的,當切換到walt governor后,執行了 per_cpu(cpufreq_update_util_data, cpu) = NULL,就摒棄了原生的調頻接口。
(1) 注冊/注銷調頻回調
struct cpufreq_governor walt_gov //cpufreq_walt.c waltgov_start //.start 回調 for_each_cpu(cpu, policy->cpus) waltgov_add_callback(cpu, &wg_cpu->cb, waltgov_update_freq); //walt.h 調頻回調 cb->func = func; rcu_assign_pointer(per_cpu(waltgov_cb_data, cpu), cb); waltgov_stop //.stop回調 for_each_cpu(cpu, policy->cpus) waltgov_remove_callback(cpu); rcu_assign_pointer(per_cpu(waltgov_cb_data, cpu), NULL);
(2) walt算法調頻位置
/* 也有一些直接調用位置,這里不再列出 */ static inline void waltgov_run_callback(struct rq *rq, unsigned int flags) //walt.h walt調頻函數 { struct waltgov_callback *cb = rcu_dereference_sched(*per_cpu_ptr(&waltgov_cb_data, cpu_of(rq))); if (cb) cb->func(cb, walt_ktime_get_ns(), flags); //調用walt調頻函數 waltgov_update_freq() } static void walt_init_once(void) { init_irq_work(&walt_migration_irq_work, walt_irq_work); init_irq_work(&walt_cpufreq_irq_work, walt_irq_work); }
(3) 提頻調用路徑
//調度上調用 walt_update_task_ravg //walt.c run_walt_irq_work //walt.c walt_irq_work_queue(&walt_cpufreq_irq_work); //調度上調用 set_task_cpu //sched/core.c task切到新cpu上 fixup_busy_time //walt.c walt_irq_work_queue(&walt_migration_irq_work); walt_irq_work waltgov_run_callback
