cpufreq_schedutil.c详细解读

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include "sched.h"

#include <linux/sched/cpufreq.h>
#include <trace/events/power.h>
#include <trace/hooks/sched.h>

#define IOWAIT_BOOST_MIN    (SCHED_CAPACITY_SCALE / 8)

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt:

这行定义了一个宏,用于格式化内核日志输出。它会在日志消息前自动加上模块名(KBUILD_MODNAME)和冒号。例如,如果模块名叫 sched,调用 pr_info("测试") 会输出 sched: 测试。这是 Linux 内核中常见的日志标识方式。

#include "sched.h":
包含 Linux 内核调度器的内部头文件,提供了调度相关的函数、数据结构(如 task_struct)等定义。

#include <linux/sched/cpufreq.h>:
包含 CPU 频率调整相关的头文件,提供调度器与 CPU 频率管理(cpufreq)交互的接口,比如请求频率提升。

#include <trace/events/power.h>:
包含电源管理相关的跟踪点定义,用于记录 CPU 频率变化或空闲状态等事件,便于调试和性能分析。

#include <trace/hooks/sched.h>:
包含调度器事件的跟踪钩子,允许在不修改核心代码的情况下,添加自定义跟踪或回调功能来监控调度行为。

#define IOWAIT_BOOST_MIN (SCHED_CAPACITY_SCALE / 8):
定义了 I/O 等待任务的最小频率提升值。SCHED_CAPACITY_SCALE 通常是 1024,表示 CPU 的最大容量。

因此,IOWAIT_BOOST_MIN = 1024 / 8 = 128,表示为等待 I/O 的任务提供至少 1/8 的 CPU 性能提升(比如提高 CPU 频率)。

整体作用

sugov_tunables 结构体

struct sugov_tunables {
    struct gov_attr_set attr_set;
    unsigned int rate_limit_us;
};

struct gov_attr_set attr_set;
这是一个嵌入的结构体,类型为 gov_attr_set,通常用于管理调度器频率调节器的可调参数(tunables)。
它提供了内核模块与用户空间交互的接口,允许通过 sysfs 文件系统动态调整参数(例如,读取或设置频率调节策略)。
具体来说,attr_set 包含一组属性(attributes),用于存储和管理参数的集合。
unsigned int rate_limit_us;
这是一个无符号整数,表示频率调整的速率限制,单位是微秒(us)。
它控制 schedutil 调度器多久调整一次 CPU 频率,以避免过于频繁的频率变化(减少开销和抖动)。
例如,如果 rate_limit_us 设置为 10000,则频率调整至少间隔 10 毫秒。

sugov_policy 结构体

struct sugov_policy {
//指向关联的 CPU 频率策略,包含频率范围(最小/最大频率)、当前频率等信息。
//每个 sugov_policy 对应一组 CPU 核心的频率管理策略。
    struct cpufreq_policy   *policy;
//指向 sugov_tunables 结构体,存储可调参数(如 rate_limit_us),用于配置频率调整行为。
    struct sugov_tunables   *tunables;
//链表节点,将该 sugov_policy 加入全局 tunables 链表,便于管理多个策略的调谐参数。
    struct list_head    tunables_hook;
//自旋锁,保护共享策略(多核共享同一频率策略)时的频率更新操作,确保线程安全。
    raw_spinlock_t      update_lock;    /* For shared policies */
//上次频率更新的时间(纳秒),用于计算下次更新的时间间隔。
    u64         last_freq_update_time;
//频率更新的最小时间间隔(纳秒),基于 tunables->rate_limit_us,避免频繁调整频率。
    s64         freq_update_delay_ns;
//计划设置的下一个 CPU 频率(kHz),在下次更新时应用。
    unsigned int        next_freq;
//缓存的原始频率值,用于优化频率计算或避免重复处理。
    unsigned int        cached_raw_freq;

    /* The next fields are only needed if fast switch cannot be used: */
//以下字段仅在不支持快速频率切换(fast switch)时使用:
//中断上下文中的异步工作,用于触发频率调整。
    struct          irq_work irq_work;
//内核线程的工作项,用于异步执行频率调整。
    struct          kthread_work work;
//互斥锁,保护工作项的并发访问。
    struct          mutex work_lock;
//内核线程工作者,负责执行频率调整任务。
    struct          kthread_worker worker;
//指向执行频率调整的内核线程。
    struct task_struct  *thread;
//标志位,表示频率调整是否正在进行,防止重复调度。
    bool            work_in_progress;
//标志位,表示频率限制(如最大/最小频率)是否改变,需重新评估频率。
    bool            limits_changed;
//标志位,表示是否需要更新频率(例如,负载变化触发)。
    bool            need_freq_update;
};

sugov_cpu 结构体

struct sugov_cpu {
//指向用于更新 CPU 利用率数据的结构体,包含回调函数(如 update_util),
//由调度器调用以通知 schedutil 关于 CPU 负载的变化。
//它帮助 schedutil 根据任务负载调整 CPU 频率。
    struct update_util_data update_util;
//指向该 CPU 关联的 sugov_policy 结构体,包含频率策略和调谐参数(如 tunables)。
//一个 sugov_policy 可能管理多个 CPU(共享策略),而 sugov_cpu 是单个 CPU 的数据。
    struct sugov_policy *sg_policy;
//该结构对应的 CPU 核心编号,用于标识特定的 CPU。
    unsigned int        cpu;
//标志位,表示是否有等待应用的 I/O 提升(boost)。用于延迟应用 I/O 提升以优化性能。
    bool            iowait_boost_pending;
//当前为 I/O 等待任务设置的频率提升值(基于 SCHED_CAPACITY_SCALE,如您之前提到的 IOWAIT_BOOST_MIN = SCHED_CAPACITY_SCALE / 8)。
//它确保 I/O 密集型任务在调度时获得更高的 CPU 频率以减少延迟。
    unsigned int        iowait_boost;
//上次利用率更新的时间(纳秒),用于跟踪 CPU 负载变化的时机。
    u64         last_update;
//表示该 CPU 上 deadline 调度类任务的带宽需求(bandwidth for deadline tasks)。
//用于确保实时任务的频率需求得到满足。
    unsigned long       bw_dl;
//该 CPU 的最大容量(通常基于 SCHED_CAPACITY_SCALE),表示硬件支持的最大性能。
    unsigned long       max;

    /* The field below is for single-CPU policies only: */
#ifdef CONFIG_NO_HZ_COMMON
//保存的空闲调用计数,用于支持动态时钟(tickless)系统。
//在单 CPU 策略中,记录 CPU 进入空闲状态的次数,帮助优化能耗(与 NO_HZ 机制相关)。
    unsigned long       saved_idle_calls;
#endif
};

DEFINE_PER_CPU

static DEFINE_PER_CPU(struct sugov_cpu, sugov_cpu);
DEFINE_PER_CPU:
这是一个 Linux 内核宏,用于为每个 CPU 核心分配一个独立的 struct sugov_cpu 实例。
struct sugov_cpu:
如您之前提供的定义,struct sugov_cpu 存储单个 CPU 核心的频率调整状态,包括利用率数据(update_util)、I/O 提升(iowait_boost)、上次更新时间(last_update)等。
sugov_cpu:
这是变量的名称,定义了一个全局的每 CPU 变量,供 schedutil 代码访问每个 CPU 的 sugov_cpu 实例。
例如,CPU0 的 sugov_cpu 数据独立于 CPU1 的数据。
static:
表示该变量具有文件作用域,仅在定义它的源文件中可见,防止外部模块直接访问。

sugov_should_update_freq结构体

static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
{
    s64 delta_ns;
     //检查当前 CPU 是否支持频率更新(通过 cpufreq_this_cpu_can_update)。
     //如果硬件不支持跨 CPU 频率请求(例如,快速切换平台无法处理远程 CPU 的请求),
     //直接返回 false,避免无意义的频率计算。
    if (!cpufreq_this_cpu_can_update(sg_policy->policy))
        return false;
//检查 limits_changed 标志(sugov_policy 字段),表示频率限制(如最大/最小频率)是否发生变化。
//如果 limits_changed 为 true:清除标志(limits_changed = false)。
//设置 need_freq_update = true,标记需要频率更新。
//直接返回 true,触发频率调整。
//unlikely 提示编译器此条件不常发生,优化分支预测。
    if (unlikely(sg_policy->limits_changed)) {
        sg_policy->limits_changed = false;
        sg_policy->need_freq_update = true;
        return true;
    }
//计算当前时间与上次频率更新时间(last_freq_update_time)的差值(纳秒)。
//last_freq_update_time 是 sugov_policy 字段,记录上次 记录上次频率调整的时间。
    delta_ns = time - sg_policy->last_freq_update_time;
//比较时间差 delta_ns 与频率更新间隔 freq_update_delay_ns(sugov_policy 字段,基于 tunables->rate_limit_us)。
//如果 delta_ns 大于或等于 freq_update_delay_ns,返回 true,表示需要更新频率;否则返回 false。
    return delta_ns >= sg_policy->freq_update_delay_ns;
 }

sugov_update_next_freq

//sg_policy:指向 struct sugov_policy,包含频率策略相关数据(如 next_freq、last_freq_update_time)。
//time:当前时间(纳秒),用于更新频率调整时间戳。
//next_freq:建议的下一个 CPU 频率(kHz)。
static bool sugov_update_next_freq(struct sugov_policy *sg_policy, u64 time,
                   unsigned int next_freq)
{
//检查 need_freq_update 标志(sugov_policy 字段),表示是否需要强制更新频率(例如,负载变化或限制更改)。
//如果 need_freq_update 为 false(不需要强制更新):
//检查当前 next_freq(sg_policy->next_freq)是否与建议的 next_freq 相同。
//如果相同,返回 false,表示无需更新频率(优化,避免重复设置)。

    if (!sg_policy->need_freq_update) {
        if (sg_policy->next_freq == next_freq)
            return false;
    } else {
    //如果 need_freq_update 为 true(需要强制更新):
//调用 cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS),检查硬件驱动是否要求更新频率限制(如最大/最小频率)。
//更新 need_freq_update 标志,决定后续是否继续需要强制更新。
//这确保在频率限制变化时(如用户调整 sysfs 参数),频率更新符合硬件要求。
        sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
    }
//将建议的 next_freq 存储到 sugov_policy->next_freq,作为下次频率调整的目标值。
    sg_policy->next_freq = next_freq;
//更新 last_freq_update_time 为当前时间,记录频率更新的时间戳。
//这与 sugov_should_update_freq 中的时间间隔检查(freq_update_delay_ns)相关。
    sg_policy->last_freq_update_time = time;
//返回 true,表示频率已更新(或需要应用),通知调用者继续处理(如触发 cpufreq 驱动调整频率)。
    return true;
}

sugov_fast_switch

static void sugov_fast_switch(struct sugov_policy *sg_policy, u64 time,
                  unsigned int next_freq)
{
    if (sugov_update_next_freq(sg_policy, time, next_freq))
    //调用 cpufreq_driver_fast_switch,通过硬件驱动直接设置 CPU 频率为 next_freq。
        cpufreq_driver_fast_switch(sg_policy->policy, next_freq);
}

sugov_deferred_update

//用于在不支持快速频率切换(fast switch)的硬件上异步更新 CPU 频率。
static void sugov_deferred_update(struct sugov_policy *sg_policy, u64 time,
unsigned int next_freq)
{
//如果 sugov_update_next_freq 返回 false(频率未变且无需强制更新),直接退出函数,避免不必要的异步处理。
//如果返回 true(频率需要更新),继续执行后续逻辑。
if (!sugov_update_next_freq(sg_policy, time, next_freq))
return;
//检查 work_in_progress 标志(sugov_policy 字段),表示是否有频率调整工作正在进行。
//如果 work_in_progress 为 false(无工作进行)
//设置 work_in_progress = true,标记工作开始,防止重复调度。
if (!sg_policy->work_in_progress) {
sg_policy->work_in_progress = true;
//调用 irq_work_queue(&sg_policy->irq_work),将 sg_policy->irq_work 加入中断工作队列,异步触发频率调整。
irq_work_queue(&sg_policy->irq_work);
}
}

get_next_freq

//get_next_freq 是 schedutil 调度器的核心函数,根据 CPU 利用率(util)和容量(max)计算目标频率。
static unsigned int get_next_freq(struct sugov_policy *sg_policy,
unsigned long util, unsigned long max)
{
//获取 sg_policy 中的 cpufreq_policy,包含频率策略信息(如最大频率 cpuinfo.max_freq、当前频率 cur、最小/最大限制)。
struct cpufreq_policy *policy = sg_policy->policy;
//根据硬件是否支持频率不变(frequency-invariant)选择基准频率:
//如果支持(arch_scale_freq_invariant() 返回 true),使用最大频率 cpuinfo.max_freq。
//否则,使用当前频率 policy->cur。
//频率不变意味着利用率已标准化,基于最大频率计算;否则,需根据当前频率调整。
unsigned int freq = arch_scale_freq_invariant() ?
policy->cpuinfo.max_freq : policy->cur;
unsigned long next_freq = 0;
//调用 Android 特定的跟踪钩子(vendor hook),允许外部模块(如 Android 厂商驱动)修改频率计算。
//参数包括 util、基准 freq、最大容量 max,输出到 next_freq 和 need_freq_update。
//这是一个扩展点,常用于 Android 设备优化频率选择。
trace_android_vh_map_util_freq(util, freq, max, &next_freq, policy,
&sg_policy->need_freq_update);
//如果 next_freq 非 0(外部钩子提供了频率),使用 next_freq。
if (next_freq)
freq = next_freq;
else
//否则,调用 map_util_freq 计算频率:
//对于频率不变:next_freq = 1.25 * max_freq * util / max(注释中的公式,C=1.25,tipping point 为 util/max=0.8)。
//对于非频率不变:next_freq = 1.25 * curr_freq * util / max。
//系数 1.25 放大利用率,确保在 80% 利用率时达到最大频率。
freq = map_util_freq(util, freq, max);
//检查计算出的 freq 是否与缓存的 cached_raw_freq 相同,且无需强制更新(need_freq_update 为 false)。
//如果是,返回当前 next_freq,避免重复计算和驱动调用,优化性能。
if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
return sg_policy->next_freq;
//更新 cached_raw_freq,缓存本次计算的原始频率,用于下次比较。
sg_policy->cached_raw_freq = freq;
//调用 cpufreq_driver_resolve_freq,将计算的 freq 转换为驱动支持的最接近频率(不低于 freq),并确保在策略的 min 和 max 范围内。
//返回最终的频率值(kHz)。
return cpufreq_driver_resolve_freq(policy, freq);
}

schedutil_cpu_util

//cpu:目标 CPU 的编号。
//util_cfs:CFS(Completely Fair Scheduler)任务的利用率。
//max:CPU 的最大容量(通常为 SCHED_CAPACITY_SCALE,即 1024)。
//type:利用率类型,FREQUENCY_UTIL(频率选择)或 ENERGY_UTIL(能耗计算)。
//p:指向任务结构体(task_struct),用于利用率钳制(uclamp)。
unsigned long schedutil_cpu_util(int cpu, unsigned long util_cfs,
unsigned long max, enum schedutil_type type,
struct task_struct *p)
{
unsigned long dl_util, util, irq;
//获取 CPU 的运行队列(rq)。
struct rq *rq = cpu_rq(cpu);
//如果未启用利用率钳制(uclamp)、类型为 FREQUENCY_UTIL 且 RT(实时)任务可运行,直接返回最大容量(max)
//这是因为 RT 任务优先级高,需以最大频率运行。
if (!uclamp_is_used() &&
type == FREQUENCY_UTIL && rt_rq_is_runnable(&rq->rt)) {
return max;
}

//获取 IRQ/steal 时间利用率(irq)。
//如果 IRQ 利用率饱和(irq >= max),返回最大容量,表示 CPU 被中断或 steal 时间占满,需最高频率。

irq = cpu_util_irq(rq);
if (unlikely(irq >= max))
    return max;

//将 CFS 利用率(util_cfs)和 RT 利用率(cpu_util_rt)相加。
//如果是频率选择(FREQUENCY_UTIL),应用利用率钳制(uclamp),根据任务 p 的约束调整 util(如限制最大/最小频率)。
util = util_cfs + cpu_util_rt(rq);
if (type == FREQUENCY_UTIL)
util = uclamp_rq_util_with(rq, util, p);

//获取 DL(deadline)任务的利用率(cpu_util_dl)。
//如果 CFS+RT+DL 利用率之和饱和(>= max),返回最大容量,表示无空闲时间,需最高频率。

dl_util = cpu_util_dl(rq);

if (util + dl_util >= max)
    return max;

//如果是能耗计算(ENERGY_UTIL),将 DL 利用率加入 util,以估算实际运行时间(包括 DL 任务)。
if (type == ENERGY_UTIL)
util += dl_util;

/*
 * There is still idle time; further improve the number by using the
 * irq metric. Because IRQ/steal time is hidden from the task clock we
 * need to scale the task numbers:
 *
 *              max - irq
 *   U' = irq + --------- * U
 *                 max
 */

//使用公式 U' = irq + (max - irq) / max * U 缩放 util,补偿 IRQ/steal 时间对任务时钟的隐藏影响。
//然后加上 irq,得到更准确的利用率。
util = scale_irq_capacity(util, irq, max);
util += irq;

/*
 * Bandwidth required by DEADLINE must always be granted while, for
 * FAIR and RT, we use blocked utilization of IDLE CPUs as a mechanism
 * to gracefully reduce the frequency when no tasks show up for longer
 * periods of time.
 *
 * Ideally we would like to set bw_dl as min/guaranteed freq and util +
 * bw_dl as requested freq. However, cpufreq is not yet ready for such
 * an interface. So, we only do the latter for now.
 */
 如果是频率选择,将 DL 任务的带宽需求(cpu_bw_dl)加入 util,确保满足 deadline 任务的最小频率要求。
if (type == FREQUENCY_UTIL)
    util += cpu_bw_dl(rq);

//返回 util 和 max 的最小值,确保利用率不超过最大容量。
return min(max, util);
}

EXPORT_SYMBOL_GPL

EXPORT_SYMBOL_GPL:
这是一个 Linux 内核宏,用于将指定的函数或变量符号(这里是 schedutil_cpu_util)导出到内核符号表。

导出的符号可以被其他内核模块(动态加载的模块)访问和调用。

GPL 后缀表示该符号仅限于 GPL 许可的模块使用,限制非 GPL 模块访问,遵循 Linux 内核的许可要求。

schedutil_cpu_util:
如您之前提供的代码,schedutil_cpu_util 是一个函数,计算 CPU 的有效利用率(基于 CFS、RT、DL 任务、IRQ 时间和 DL 带宽),用于频率选择(FREQUENCY_UTIL)或能耗计算(ENERGY_UTIL)。

它的输出(利用率)被 get_next_freq 使用,影响 CPU 频率调整。

作用:
通过 EXPORT_SYMBOL_GPL,schedutil_cpu_util 函数对其他 GPL 许可的内核模块开放,例如:
其他频率调节器模块,可能需要 CPU 利用率数据。

能耗管理模块(如 Energy Aware Scheduling, EAS),用于优化调度决策。

厂商定制模块(如 Android 的电源管理扩展)。

这提高了内核模块的复用性和扩展性,允许外部模块利用 schedutil 的利用率计算逻辑。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容