linux cpufreq framework(3)_cpufreq core

1. 前言

前文(Linux cpufreq framework(2)_cpufreq driver)从平台驱动工程师的角度,简单的介绍了编写一个cpufreq driver的大概步骤。但要更深入理解、更灵活的使用,必须理解其内部的实现逻辑。

因此,本文将从cpufreq framework core的角度,对cpufreq framework的内部实现做一个简单的分析。

2. 提供给用户空间的接口

cpufreq core的内部实现,比较繁杂,因此我们需要一个分析的突破口,而cpufreq framework提供的API是一个不错的选择。因为API抽象了cpufreq的功能,而所有的cpufreq core、cpufreq driver等模块,都是为这些功能服务的。

由“linux cpufreq framework(1)_概述”的介绍可知,cpufreq framework通过sysfs向用户空间提供接口,具体如下:

/sys/devices/system/cpu/cpu0/cpufreq/
|-- affected_cpus
|-- cpuinfo_cur_freq
|-- cpuinfo_max_freq
|-- cpuinfo_min_freq
|-- cpuinfo_transition_latency
|-- related_cpus
|-- scaling_available_frequencies
|-- scaling_available_governors
|-- scaling_cur_freq
|-- scaling_driver
|-- scaling_governor
|-- scaling_max_freq
|-- scaling_min_freq
|-- scaling_setspeed
`—stats
    |-- time_in_state
    |-- total_trans
    `-- trans_table

1)“cpufreq”目录

在kernel的模型中,cpufreq被抽象为cpu device的一个interface,因此它位于对应的cpu目录(/sys/devices/system/cpu/cpuX)下面。

有些平台,所有cpu core的频率和电压时统一控制的,即改变某个core上的频率,其它core同样受影响。此时只需要实现其中一个core(通常为cpu0)的cpufreq即可,其它core的cpufreq直接是cpu0的符号链接。因此,使用这些API时,随便进入某一个cpu下面的cpufreq目录即可。

而另一些些平台,不同core可以单独控制,这时不同cpu目录下的cpufreq就不一样了。

到底某一个cpufreq可以控制多少cpu core呢?可以通过cpufreq/affected_cpus和cpufreq/related_cpus两个文件查看,其中的区别是:affected_cpus表示该cpufreq影响到哪些cpu core(没有显示处于offline状态的cpu),related_cpus则包括了online+offline的所有core。

2)cpuinfo相关的信息

由“Linux cpufreq framework(2)_cpufreq driver”的描述可知,cpufreq driver初始化时,会根据frequency table等信息,填充struct cpufreq_policy变量中的struct cpufreq_cpuinfo变量,该变量保存了CPU调频有关的固定信息(不可以在运行过程中修改,主要包括:最大频率(cpuinfo_max_freq)、最小频率(cpuinfo_min_freq)、频率转换延迟(cpuinfo_transition_latency )。

另外,通过cpuinfo_cur_freq ,可以获取cpu core的当前频率(真实的、cpu的当前运行频率,会通过cpufreq_driver->get回调读取)。

当前,这四个“cpuinfo_”开头的sysfs API,都是只读的。

3)scaling_available_frequencies,获取当前可以配置的频率列表,从frequency table直接读取。readonly。

4) scaling_driver,当前加载的cpufreq driver名称,readonly。

5)scaling_available_governors和scaling_governor,系统中可用的governor列表,以及当前使用的governor。governor相关的内容会在后续详细描述。scaling_available_governors是readonly。scaling_governor可以更改为scaling_available_governors中的值。

6)scaling_cur_freq,从cpufreq core或者governor的角度,看到的当前频率,和cpuinfo_cur_freq的意义不相同,后面的分析中根据实例在描述。readonly。

7)scaling_max_freq、scaling_min_freq和scaling_setspeed

scaling_max_freq和scaling_min_freq表示调频策略所允许的最大和最小频率,对于可以自动调整频率的cpu,修改它们,就是最终的频率调整。

对不能自动调整频率的cpu,则需要通过其它方式,主动的设置cpu频率,这些都是由具体的governor完成。其中有一个特例:

如果使用的governor是“userspace” governor,则可以通过scaling_setspeed节点,直接修改cpu频率。

3. 频率调整的步骤

开始分析之前,我们先以“userspace” governor为例,介绍一下频率调整的步骤。“userspace”governor是所有governor中最简单的一个,同时又是驱动工程师比较常用的一个,借助它,可以从用户空间修改cpu的频率,操作方法如下(为了简单,以shell脚本的形式给出):

cd /sys/devices/system/cpu/cpu0/cpufreq/
cat cpuinfo_max_freq; cat cpuinfo_min_freq            #获取“物理”上的频率范围 

cat scaling_available_frequencies                          #获取可用的频率列表
cat scaling_available_governors                             #获取可用的governors
cat scaling_governor                                             #当前的governor
cat cpuinfo_cur_freq; cat scaling_cur_freq              #获取当前的频率信息,可以比较一下是否不同

cat scaling_max_freq; cat scaling_min_freq           #获取当前调频策略所限定的频率范围

#假设CPU不可以自动调整频率
echo userspace > scaling_governor                      #governor切换为userspace

#如果需要切换的频率值在scaling_available_frequencies内,且在cpuinfo_max_freq/cpuinfo_min_freq的范围内。

#如果需要切换的频率不在scaling_max_freq/scaling_min_freq的范围内,修改这两个值
echo xxx > scaling_max_freq; echo xxx > scaling_min_freq

#最后,设置频率值
echo xxx > scaling_setspeed 

4. 内部逻辑分析

4.1 初始化

基于linux设备模型的思想,kernel会使用device和driver两个实体,抽象设备及其驱动,当这两个实体同时存在时,则执行driver的初始化接口(即driver开始运行)。cpufreq也不例外,基本遵循了上面的思路。但由于cpufreq是一类比较特殊的设备(它只是cpu device的一个功能,本身并不以任何形式存在),在实现上,就有点迂回。

首先,driver的抽象比较容易理解,就是我们在“Linux cpufreq framework(2)_cpufreq driver”中描述的struct cpufreq_driver结构。那device呢?先看看下面的图片:

注1:该图片不包括CPU hotplug的情况,hotplug时,会走两外一个流程,大概原理类似,本文就不详细介绍这种情况了。

在这里插入图片描述
1)cpufreq_interface

前面讲过,cpufreq driver注册时,会调用subsys_interface_register接口,注册一个subsystem interface,该interface的定义如下:

  1: /* drivers/cpufreq/cpufreq.c */
  2: static struct subsys_interface cpufreq_interface = {
  3: 	.name		= "cpufreq",
  4: 	.subsys		= &cpu_subsys,
  5: 	.add_dev	= cpufreq_add_dev,
  6: 	.remove_dev	= cpufreq_remove_dev,
  7: };
interface的subsys是“cpu_subsys”,就是cpu bus(struct bus_type cpu_subsys),提供了add_dev和remove_dev两个回调函数。

由“Linux设备模型(6)_Bus”中的描述可知,当bus下有设备probe的时候(此处为cpu device的probe),会调用其下所有interface的add_dev回调函数。物理意义是什么呢?

cpufreq是cpu device的一个功能,当cpu device开始枚举时,当然要创建代表该功能(cpufreq)的设备。而这个设备的具体形态,只有该功能相关的代码(cpufreq core)知道,因此就只能交给它了。

4.2 频率调整

cpufreq framework的频率调整逻辑,总结如下:

    通过调整policy(struct cpufreq_policy),确定CPU频率调整的一个大方向,主要是由min_freq和max_freq组成的频率范围;

    通过cpufreq governor,确定最终的频率值。

    下面结合代码,做进一步的阐述。

1)cpufreq_set_policy

cpufreq_set_policy用来设置一个新的cpufreq policy,调用的时机包括:

    a)初始化时(__cpufreq_add_dev->cpufreq_init_policy->cpufreq_set_policy),将cpufreq_driver->init时提供的基础policy,设置生效。

    b)修改scaling_max_freq或scaling_min_freq时(store_one->cpufreq_set_policy),将用户空间设置的新的频率范围,设置生效。

    c)修改cpufreq governor时(scaling_governor->store_scaling_governor->cpufreq_set_policy),更新governor。

来看一下cpufreq_set_policy都做了什么事情。

cpufreq_set_policy - 修改 cpufreq 策略参数

    @policy: 要修改的策略对象。
    @new_gov: 新的策略调速器指针。
    @new_pol: 策略值(针对带有内置调速器的驱动程序)。

该函数执行以下操作:

    调用 cpufreq 驱动程序的 ->verify() 回调函数来对要设置的频率限制进行合理性检查。
    使用验证后的限制值更新 @policy。
    如果驱动程序有 ->setpolicy() 回调函数,则调用它;否则,对 @policy 执行调速器更新。
        调速器更新包括:
            如果 @new_gov 指向与 @policy 中相同的对象,则运行当前调速器的 ->limits() 回调函数。
            否则,用 @new_gov 替换 @policy 的调速器。

注意: 该函数不会更新 @policy 的 cpuinfo 部分。
// cpufreq_set_policy 函数用于修改 CPU 频率策略参数。
static int cpufreq_set_policy(struct cpufreq_policy *policy,
                              struct cpufreq_governor *new_gov,
                              unsigned int new_pol) {
    // 声明新的策略数据结构体
    struct cpufreq_policy_data new_data;
    // 声明旧的调速器指针
    struct cpufreq_governor *old_gov;
    int ret;

    // 复制部分策略数据到新的结构体中
    memcpy(&new_data.cpuinfo, &policy->cpuinfo, sizeof(policy->cpuinfo));
    new_data.freq_table = policy->freq_table;
    new_data.cpu = policy->cpu;

    /*
     * 从 PM QoS 框架中获取最小和最大频率限制。
     */
    new_data.min = freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN);
    new_data.max = freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX);

    // 打印调试信息
    pr_debug("setting new policy for CPU %u: %u - %u kHz\n",
             new_data.cpu, new_data.min, new_data.max);

    /*
     * 调用驱动程序的 verify 回调函数来验证频率限制并确保 min <= max。
     */
    ret = cpufreq_driver->verify(&new_data);
    if (ret) {
        // 如果验证失败,则返回错误
        return ret;
    }

    /*
     * 将新的最小和最大频率限制写入策略对象。
     */
    policy->min = new_data.min;
    policy->max = new_data.max;
    policy->min = __resolve_freq(policy, policy->min, CPUFREQ_RELATION_L);
    policy->max = __resolve_freq(policy, policy->max, CPUFREQ_RELATION_H);
    trace_cpu_frequency_limits(policy);

    // 清除缓存的目标频率
    policy->cached_target_freq = UINT_MAX;

    // 打印调试信息
    pr_debug("new min and max freqs are %u - %u kHz\n",
             policy->min, policy->max);

    /*
     * 如果驱动程序有 setpolicy 回调函数,则调用它来设置频率范围。
     */
    if (cpufreq_driver->setpolicy) {
        policy->policy = new_pol;
        pr_debug("setting range\n");
        return cpufreq_driver->setpolicy(policy);
    }

    /*
     * 如果新的调速器与当前调速器相同,则只需更新调速器的限制。
     */
    if (new_gov == policy->governor) {
        pr_debug("governor limits update\n");
        cpufreq_governor_limits(policy);
        return 0;
    }

    // 打印调试信息
    pr_debug("governor switch\n");

    /*
     * 保存旧的调速器指针并停止旧的调速器。
     */
    old_gov = policy->governor;
    if (old_gov) {
        cpufreq_stop_governor(policy);
        cpufreq_exit_governor(policy);
    }

    /*
     * 启动新的调速器。
     */
    policy->governor = new_gov;
    ret = cpufreq_init_governor(policy);
    if (!ret) {
        ret = cpufreq_start_governor(policy);
        if (!ret) {
            pr_debug("governor change\n");
            return 0;
        }
        cpufreq_exit_governor(policy);
    }

    /*
     * 如果新的调速器启动失败,则重新启动旧的调速器。
     */
    pr_debug("starting governor %s failed\n", policy->governor->name);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值