基于 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。