一、系統CPU算力
1. 查看系統CPU算力
(1) 設備樹中各個CPU原始算力配置
dtb文件位置: android/out/target/product/<project_name>/obj/kernel/msm-5.4/arch/arm64/boot/dts/project_name.dtb 反解析: $ dtc -I dtb -O dts -o project_name.dts project_name.dtb 反解析設備樹,各個CPU的算力配置: capacity-dmips-mhz = <0x400>; //小核,轉為10進制為 1024 capacity-dmips-mhz = <0x6cc>; //大核,轉為10進制為 1740
(2) cat cpu_capacity 節點得到的算力
/sys/devices/system # find ./ -name cpu_capacity | xargs cat 533 1024 每個cpu最大頻點: /sys/devices/system # find ./ -name scaling_available_frequencies | xargs cat ... 1804800 2035200 ... 1708800 1804800
2. cpu_capacity 的設置
/* * cpu_capacity 來自設備樹 "capacity-dmips-mhz" 字段,保存在 raw_capacity[cpu]。這個函數最先調用, * 之后每個cluster的cpufreq執行時再唯一通過notifier機制調用一次類似的處理,完成capacity的初始化。 * 對每個cluster的每個cpu核都會調用這個函數,這個函數是最先調用的。 */ bool __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu) { //arch_topology.c /* cpu_capacity 來自設備樹 "capacity-dmips-mhz" 字段,保存在 raw_capacity[cpu] 中*/ ret = of_property_read_u32(cpu_node, "capacity-dmips-mhz", &cpu_capacity); if (!ret) { capacity_scale = max(cpu_capacity, capacity_scale); //此時 capacity_scale 保存的是設備樹中指定的算力最大的那個cpu的算力值 raw_capacity[cpu] = cpu_capacity; //此時 raw_capacity[cpu] 保存的還是設備樹中為每個cpu指定的算力 pr_debug("cpu_capacity: %pOF cpu_capacity=%u (raw)\n", cpu_node, raw_capacity[cpu]); } }
調用路徑:
init_cpu_topology parse_dt_topology parse_cluster parse_core __init get_cpu_for_node(struct device_node *node) topology_parse_cpu_capacity(cpu_node, cpu); //最先調用(1)
下面看 topology_normalize_cpu_scale 函數,它負責對CPU算力進行歸一化到1024:
void topology_normalize_cpu_scale(void) { //arch_topology.c pr_debug("cpu_capacity: capacity_scale=%u\n", capacity_scale); /* * 第二調用(2)時還沒沒有考慮頻點,直接是每個cpu來自設備樹"capacity-dmips-mhz"指定的算力與最大值PK的。 * 第三調用(3)是在 init_cpu_capacity_callback 下調用的, */ capacity = (raw_capacity[cpu] << SCHED_CAPACITY_SHIFT) / capacity_scale; topology_set_cpu_scale(cpu, capacity) per_cpu(cpu_scale, cpu) = capacity; //寫給 per-cpu 變量 cpu_scale }
調用路徑:
init_cpu_topology //arch_topology.c parse_dt_topology //第二調用(2),調用時所有cpu已經parse完了 init_cpu_capacity_callback //后調用,就是上面說的,所有cluster的cpufreq策略online后調用一次 topology_normalize_cpu_scale //第三調用(3)
之后就是每個cpufreq策略初始化OK后進行通知更新了:
static struct notifier_block init_cpu_capacity_notifier = { .notifier_call = init_cpu_capacity_callback, }; static int __init register_cpufreq_notifier(void) { /* 所有的 possible cpu 都拷貝到 cpus_to_visit*/ cpumask_copy(cpus_to_visit, cpu_possible_mask); /* 此 notifier 的回調函數為 init_cpu_capacity_callback */ ret = cpufreq_register_notifier(&init_cpu_capacity_notifier, CPUFREQ_POLICY_NOTIFIER); } core_initcall(register_cpufreq_notifier); //注冊的還挺早 /* 這個函數是每個cluster的cpufreq策略ok后都會調用 */ static int init_cpu_capacity_callback(struct notifier_block *nb, unsigned long val, void *data) //arch_topology.c { struct cpufreq_policy *policy = data; if (val != CPUFREQ_CREATE_POLICY) return 0; pr_debug("cpu_capacity: init cpu capacity for CPUs [%*pbl] (to_visit=%*pbl)\n", cpumask_pr_args(policy->related_cpus), cpumask_pr_args(cpus_to_visit)); /* 將此policy中的cpu從mask中清空 */ cpumask_andnot(cpus_to_visit, cpus_to_visit, policy->related_cpus); /* * 這里是每個cluster都執行一次的。先讀取之前不考慮頻點對比的scale后的cpu算力值,然后乘以最大頻點。 * 最終在 topology_normalize_cpu_scale 中的計算結果就是: * 之前算的 cpu_capacity * own(policy->cpuinfo.max_freq) / max(policy->cpuinfo.max_freq) * 相當於按最大頻點又scale了一次,最終cat cpu_capacity節點出來的值就是它了。 */ for_each_cpu(cpu, policy->related_cpus) { raw_capacity[cpu] = topology_get_cpu_scale(cpu) * policy->cpuinfo.max_freq / 1000UL; capacity_scale = max(raw_capacity[cpu], capacity_scale); } /* 完全為空才調用,也就是說所有cluster的policy都調用過后才能進入 */ if (cpumask_empty(cpus_to_visit)) { topology_normalize_cpu_scale(); //考慮最大頻點再次scale,也就是說設備樹中配置算力時是不用考慮頻點的 walt_update_cluster_topology(); //只會調用一次 schedule_work(&update_topology_flags_work); free_raw_capacity(); pr_debug("cpu_capacity: parsing done\n"); /* 然后觸發調用 parsing_done_workfn,取消這個notifier的通知,也就是說這個if語句體只會進來一次 */ schedule_work(&parsing_done_work); } return 0; } static void parsing_done_workfn(struct work_struct *work) { /*parse done 后就取消注冊了*/ cpufreq_unregister_notifier(&init_cpu_capacity_notifier, CPUFREQ_POLICY_NOTIFIER); }
接下來就是在 drivers/cpufreq/cpufreq.c 中,只是在 cpufreq_online 中 notifier 通知一次:
static int cpufreq_online(unsigned int cpu) { ... blocking_notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_CREATE_POLICY, policy); ... }
cpufreq_online 調用路徑:
cpufreq_interface.add_dev //回調 cpufreq_add_dev //cpufreq.c cpufreq_register_driver cpuhp_cpufreq_online //cpufreq.c cpufreq_online
由上,cat /sys/devices/system/cpu/cpuX/cpu_capacity 獲取到的CPU的算力為:own(capacity-dmips-mhz)/max(capacity-dmips-mhz) * (own(max_freq)/max(max_freq)) * 1024
大核:1740/1740 * (2035200/2035200) * 1024 = 1024 小核:1024/1740 * (1804800/2035200) * 1024 = 533
cat cpu_capacity 得到的算力是每個CPU核在最大頻點上對應的算力,注意是最大頻點的。若是想獲取當前頻點的算力或cpufreq policy內的最大算力,還要拿freq再進行scale才行。
3. cpu_capacity 的獲取
cat /sys/devices/system/cpu/cpuX/cpu_capacity cpu_capacity_show //arch_topology.c topology_get_cpu_scale(cpu->dev.id) per_cpu(cpu_scale, cpu);
二、rq->cpu_capacity_orig 和 rq->cpu_capacity
1. rq->cpu_capacity_orig 和 rq->cpu_capacity 的賦值
(1) 更新函數
static void update_cpu_capacity(struct sched_domain *sd, int cpu) { /* 使用的是topology_get_cpu_scale(int cpu),返回的是 per_cpu(cpu_scale, cpu),就是cat cpu_capacity文件得到的那個值 */ unsigned long capacity = arch_scale_cpu_capacity(cpu); //不是使用返回的是常量值的那個,使用的是宏定義的 struct sched_group *sdg = sd->groups; /* * 使用的是 topology_get_max_freq_scale(), return per_cpu(max_freq_scale, cpu); max_freq_scale 是當前policy的最大頻 * 點相對於此cpu能達到的最大頻點的scale,見 arch_set_max_freq_scale() */ capacity *= arch_scale_max_freq_capacity(sd, cpu);//1024 capacity >>= SCHED_CAPACITY_SHIFT; //除以1024, 就相當於 capacity * (cur_freq/max_freq), 得到的是當前policy的算力 /*thermal降低它有什么影響?*/ capacity = min(capacity, thermal_cap(cpu)); //受溫控影響,取最小值,return thermal_cap_cpu[cpu] ?: SCHED_CAPACITY_SCALE; cpu_rq(cpu)->cpu_capacity_orig = capacity; //這個是取受頻點和溫控影響后的 /*就理解為返回的是 capacity- rt/dl/irq 后的吧*/ capacity = scale_rt_capacity(cpu, capacity); if (!capacity) capacity = 1; /*賦的是當前cpu最大算力按freq scale、受溫度影響、然后再減去rt/dl/irq的貢獻后的*/ cpu_rq(cpu)->cpu_capacity = capacity; sdg->sgc->capacity = capacity; sdg->sgc->min_capacity = capacity; sdg->sgc->max_capacity = capacity; }
調用路徑:
run_rebalance_domains //SCHED_SOFTIRQ 軟中斷中處理 nohz_idle_balance newidle_balance nohz_newidle_balance _nohz_idle_balance scheduler_tick //每個tick中都會觸發負載均衡 trigger_load_balance raise_softirq(SCHED_SOFTIRQ) //open_softirq(SCHED_SOFTIRQ, run_rebalance_domains); run_rebalance_domains rebalance_domains fair_sched_class.balance //回調 (各個調度類的回調沒見到有什么位置調用!) balance_fair pick_next_task_fair //沒有任務可pick時調用 newidle_balance load_balance find_busiest_group update_sd_lb_stats sched_isolate_cpu sched_unisolate_cpu_unlocked sched_update_group_capacities sched_init_domains partition_sched_domains_locked build_sched_domains init_sched_groups_capacity update_group_capacity //sd->child domain不為null的時候調用 update_cpu_capacity
(2) 下面看 per_cpu 的 max_freq_scale 的設置:
更新函數:
void arch_set_max_freq_scale(struct cpumask *cpus, unsigned long policy_max_freq) { //arch_topology.c int cpu = cpumask_first(cpus); /*獲取當前cpu的最大頻點*/ max_freq = per_cpu(max_cpu_freq, cpu); /*當policy的最大頻點乘以1024除以此CPU的最大頻點得到scale值*/ scale = (policy_max_freq << SCHED_CAPACITY_SHIFT) / max_freq; for_each_cpu(cpu, cpus) { /*將scale存儲在per-cpu變量max_freq_scale中*/ per_cpu(max_freq_scale, cpu) = scale; } }
調用路徑:
cpufreq_add_dev cpuhp_cpufreq_online cpufreq_online cpufreq_init_policy store_scaling_governor //用戶空間更改governor的設置 cpufreq_notifier_min //schedule_work(&policy->update); policy->nb_min.notifier_call = cpufreq_notifier_min; cpufreq_notifier_max //schedule_work(&policy->update); policy->nb_max.notifier_call = cpufreq_notifier_max; cpufreq_verify_current_freq //schedule_work(&policy->update); handle_update //cpufreq.c INIT_WORK(&policy->update, handle_update); cpufreq_update_policy refresh_frequency_limits cpufreq_set_policy arch_set_max_freq_scale(policy->cpus, policy->max) //從傳參可以看出是per-cluster的cpu
rq->cpu_capacity_orig 是一個policy的最大算力,是使用cpu的最大算力乘以此policy的最大頻點與cpu最大頻點的比值得到的,然后還考慮了thermal的限頻影響。cpu_rq(cpu)->cpu_capacity 是 rq->cpu_capacity_orig 中去除 rt、dl、irq 的貢獻后的。
也就是說設置 cpu的 freq limit 會改變其 rq->cpu_capacity_orig 表示的算力。CPU onlline的時候會觸發重新計算。max_freq_scale 是用來計算某個policy的最大算力的。
(3) 下面看一下 thermal_cap() 這個溫度如何影響CPU算力的:
設置位置:
unsigned long thermal_cap(int cpu) { //walt.c return thermal_cap_cpu[cpu] ?: SCHED_CAPACITY_SCALE; //不為0返回自己 } void sched_update_cpu_freq_min_max(const cpumask_t *cpus, u32 fmin, u32 fmax) { for_each_cpu(i, &cpus) thermal_cap_cpu[i] = do_thermal_cap(i, fmax); } static inline unsigned long do_thermal_cap(int cpu, unsigned long thermal_max_freq) { if (unlikely(!walt_clusters_parsed)) return capacity_orig_of(cpu); /* * arg1: return per_cpu(cpu_scale, cpu) 就是 cat cpu_capacity 文件得到的值。 * arg3: rq->wrq.cluster->max_possible_freq * cpu_capacity * thermal_max_freq / max_possible_freq 若結果有小數向上圓整 */ return mult_frac(arch_scale_cpu_capacity(cpu), thermal_max_freq, cpu_max_possible_freq(cpu)); }
調用路徑:
limits_mitigation_notify(struct limits_dcvs_hw *hw) //thermal/qcom/msm_lmh_dcvs.c limits_dcvsh_poll dcvsh_handle_isr //qcom-cpufreq-hw.c 中斷線程,對 dcvsh-irq-<cluster_first_cpu_id>進行響應的 limits_mitigation_notify(struct cpufreq_qcom *c, bool limit) sched_update_cpu_freq_min_max
也就是說 thermal 是通過限制 CPU 最大頻率來限制 CPU 算力的。然后 rq->cpu_capacity_orig 取的是 freq policy 和 thermal 兩者限制最很的。也就是說 rq->cpu_capacity_orig 是設備樹中指定的cpu的算力與超大核的算力連帶兩者的最大頻點scale后的算力然后再被 freq policy 和 thermal 再次 scale 后的算力。
2. rq->cpu_capacity_orig 的使用
限制CPU的算力有什么用呢,那就看對 rq->cpu_capacity_orig 的使用
(1) 使用位置1 —— capacity_curr_of
unsigned long capacity_curr_of(int cpu) { unsigned long max_cap = cpu_rq(cpu)->cpu_capacity_orig; /* return per_cpu(freq_scale, cpu) => freq_scale = freq_cur * 1024 / freq_max */ unsigned long scale_freq = arch_scale_freq_capacity(cpu); /* max_cap * (freq_cur * 1024 / freq_max) == max_cap * (freq_cur/freq_max) */ return cap_scale(max_cap, scale_freq); }
capacity_curr_of 的調用路徑:
try_to_wake_up //core.c 若 do_pl_notif() 返回true時才觸發調頻 do_pl_notif //walt.c 若cpu的當前(頻點)的算力已經是此freq policy下的最大算力了,就返回false walt_find_best_target //fair.c 為任務選核調用路徑 walt_adjust_cpus_for_packing //fair.c 若是評估的算力比這個cpu的當前算力大,就舍棄這個cpu。 capacity_curr_of
由 capacity_curr_of() 的調用路徑可知,在喚醒任務時會有一個提頻點通過它來判斷是否觸發提頻。在為任務選核時,通過它來判斷是否舍棄算力不足的cpu核.
capacity_curr_of(cpu) 返回的是這個 cpu 此時的算力,也就是當前頻點下的此 cpu 的算力。有一個trace sched_cpu_util 會打印這個 cpu 的當前算力值。算力與頻點掛鈎的,若判斷某個 cpu 已經達到某個cpu的最大算力了,那就遷核吧,已經是最大頻點了,沒有必要再調頻。
freq_scale 這個per-cpu變量的賦值邏輯:
是下面這個函數中賦值,由上分析可知,參數中的這個 max_freq 需要是 freq pollicy 內的最大頻點邏輯才是正常的。
void arch_set_freq_scale(struct cpumask *cpus, unsigned long cur_freq, unsigned long max_freq) //arch_topology.c { scale = (cur_freq << SCHED_CAPACITY_SHIFT) / max_freq; for_each_cpu(i, cpus){ per_cpu(freq_scale, i) = scale; per_cpu(max_cpu_freq, i) = max_freq; } }
arch_set_freq_scale 的調用路徑:
cpufreq_qcom_hw_driver.fast_switch //回調 qcom_cpufreq_hw_fast_switch cpufreq_qcom_hw_driver.target_index //回調 qcom_cpufreq_hw_target_index //qcom-cpufreq-hw.c arch_set_freq_scale //arch_topology.c
在設置CPU頻點時 arch_set_freq_scale 會被調用,更新這個 scale 值。freq_scale 表示當前正在使用的頻點對(此freq policy內的)最大頻點的scale值。
(2) 使用位置2 —— check_cpu_capacity
/* * 檢查rq的算力是否因副業活動而明顯減少。imbalance_pct表示閾值。返回true是容量減少了。 * sd->imbalance_pct 的注釋:直到觸發這個水線才進行均衡 */ static inline int check_cpu_capacity(struct rq *rq, struct sched_domain *sd) //fair.c { /* * 整理后:rq->cpu_capacity * (sd->imbalance_pct/100) < rq->cpu_capacity_orig * 明明是 cpu_capacity_orig要大一些, imbalance_pct 會取大於100的值嗎? */ return ((rq->cpu_capacity * sd->imbalance_pct) < (rq->cpu_capacity_orig * 100)); }
調用路徑:
trigger_load_balance //schedule_tick中觸發SCHED_SOFTIRQ軟中斷調用 nohz_balancer_kick //fair.c load_balance //共路徑 need_active_balance load_balance //共路徑 voluntary_active_balance //fair.c rebalance_domains //schedule_tick中觸發SCHED_SOFTIRQ軟中斷調用 newidle_balance //CPU進入空閑時均衡 load_balance find_busiest_queue //fair.c nohz_balancer_kick //共路徑 check_misfit_status //fair.c 此cpu上有misfit load的任務,且此cpu不是這個調度域中算力最大的cpu或這個函數返回true就返回真 check_cpu_capacity
由調用路徑來看,主要是在負載均衡的流程中調用。
imbalance_pct 的取值邏輯:
static struct sched_domain * sd_init(struct sched_domain_topology_level *tl, const struct cpumask *cpu_map, struct sched_domain *child, int dflags, int cpu) { //sched/topology.c struct sched_domain *sd = *per_cpu_ptr(sdd->sd, cpu); *sd = (struct sched_domain){ ... .imbalance_pct = 125, ... }
調用路徑:
sched_init_domains // partition_sched_domains_locked build_sched_domains build_sched_domain //sched/topology.c sd_init
imbalance_pct 的賦值果真是大於100的,在調度域的初始化中進行賦值。也就是說 rt/dl/irq 的負載占到cpu算力的 25% 時,就要傾向於進行負載均衡了。
(3) 使用位置3 —— check_misfit_status
static inline int check_misfit_status(struct rq *rq, struct sched_domain *sd) { return rq->misfit_task_load && (rq->cpu_capacity_orig < rq->rd->max_cpu_capacity.val || check_cpu_capacity(rq, sd)); }
調用路徑見 check_cpu_capacity() 的調用路徑,也主要是在負載均衡中調用。
rq->misfit_task_load 的賦值路徑:
static inline void update_misfit_status(struct task_struct *p, struct rq *rq) { if (!p) { rq->misfit_task_load = 0; return; } if (task_fits_max(p, cpu_of(rq))) { rq->misfit_task_load = 0; return; } /* load Qcom沒有改,只改的是util */ rq->misfit_task_load = max_t(unsigned long, task_h_load(p), 1); //return p->se.avg.load_avg; }
對於任務 p 來說算力不足對 rq->misfit_task_load 賦 p->se.avg.load_avg,若一個任務一直跑,PELT算法計算出的 load_avg 就無限接近其權重。rq->misfit_task_load 在負載均衡上使用判斷的比較多。
(4) 使用位置4 —— task_fits_capacity
/* * p->wts.demand_scaled 要大於 XX% * capacity 才返回真,使用的是rq->cpu_capacity_orig, * 這個是cpu的最大算力(cat cpu_capacity文件得到)然后按頻點scale和受到thermal影響后的 * rq->cpu_capacity_orig 來判斷是選up還是down margin的。 * * 有時調用 capacity 傳的是 rq->cpu_capacity , 有時候傳的是 rq->cpu_capacity_orig */ static inline bool task_fits_capacity(struct task_struct *p, long capacity, int cpu) { //fair.c /*return p->wts.demand_scaled 要大於 XX% * capacity 才返回true*/ return capacity * 1024 > uclamp_task_util(p) * margin; }
實測,/sys/devices/system/cpu/cpuX/cpu_capacity 的值不會隨 cpufreq policy 對最大頻點的限制而變化,就算是限制最大頻點然后睡眠喚醒后也不變,而 capacity_curr_of(cpu) 獲取到的算力會隨着限頻而變化。