调度器22—CPU频点设置函数分析


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

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM