調度器8—CPU算力 cpu_capacity


一、系統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) 獲取到的算力會隨着限頻而變化。

 


免責聲明!

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



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