zoukankan      html  css  js  c++  java
  • 调度器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)
    ", 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
    ", 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)
    ",
             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
    ");
            /* 然后触发调用 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) 获取到的算力会随着限频而变化。

  • 相关阅读:
    JS DOM基础
    JS 部分常见循环、分支、嵌套练习
    记一些让footer始终位于网页底部的方法
    JS 实现banner图的滚动和选择效果
    JS 部分基础内容总结
    Flex弹性布局基础教程
    My SQL数据库的安装与配置
    网页共用头部和尾部的部分方法
    Unity3d入门 关于unity工具的熟悉
    Unity3d学习 制作地形
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15429760.html
Copyright © 2011-2022 走看看