cpufreq是linux上负责实现动态调频的关键,这篇笔记总结了linux内核cpufreq子系统的关键实现(Linux 3.18.140)。
概述
借用一张网络上的图片来看cpufreq子系统的整体结构:
- 用户态接口:cpufreq通过sysfs向用户态暴露接口,这些节点部分是为了展示内核的配置,部分节点是可以配置的,通过这些节点可以控制cpufreq的一些行为。
- core层:cpufreq子系统的核心层,负责管理子系统中的policy、governor和driver组件,是三者的纽带,通过core层,实现了调频策略和调频机制的分离。
- policy:调频策略,每个CPU都有一个调频策略,规定了该CPU的最大、最小可运行频率等信息。
- governor:可以独立于core层实现,通过规定的接口和core层交互。policy必须和某个governor关联,由governor实现具体的调频策略。可以认为policy负责管理调频参数,governor基于调频参数实现调频策略。
- driver:平台相关的调频驱动,core层使用driver中的接口完成具体的调频操作。
- notifier:cpufreq基于通知链机制对外发布的通知事件,外部模块可以通过监听这些事件在CPU的调频策略或者频率发生变化时做出一些处理。
- stats:一些频率调整的统计信息,我们不关注。
这篇笔记我们重点关注core层的实现,其它部分模块仅作简要介绍。
core层关键实现
core层是cpufreq子系统的关键,它主要包含下面内容:
- driver的管理。
- policy的管理。
- governor的管理。
- 事件通知。
- 用户态接口。
- 为了方便driver、governor实现而提供的一些公共的API。
core层初始化
开机过程中,core层最开始的初始化非常简单,仅仅是创建一个kobject对象,该对象在sysfs中对应到目录/sys/devices/system/cpu/cpufreq。
struct kobject *cpufreq_global_kobject;
static int __init cpufreq_core_init(void)
{
if (cpufreq_disabled()) // cpu动态调频功能未使能
return -ENODEV;
// 创建kobject代表cpufreq
cpufreq_global_kobject = kobject_create();
return 0;
}
core_initcall(cpufreq_core_init);
driver的管理
cpufreq驱动的实现需要实例化一个struct cpufreq_dirver对象,然后通过cpufreq_register_driver()函数向core层注册自己。系统只需要一个cpufreq驱动来实现具体的CPU频率调整功能,所以core层只允许注册一个驱动。
struct cpufreq_driver
下面是cpufreq_driver的一些核心字段:
- init():该回调必须实现,core层在为CPU设置policy时会用分配的policy为参数调用驱动的该回调,驱动需要在实现中设置policy的部分字段。
- verify():该回调必须实现,core层会通过该回调让驱动校验policy中的参数设置是否正确。
- setpolicy()、target()、target_index():这几个回调必须实现一个。当实现setpolicy()时,驱动表示软件只需要设置一个策略参数(如功耗优先or性能优先)即可,硬件来决定具体的CPU工作频率。当实现后两个回调时,表示需要由governor来决定CPU的具体工作频率,然后通过这两个接口将CPU工作频率设定为指定值。
struct cpufreq_driver {
char name[CPUFREQ_NAME_LEN]; // 驱动名称会在sysfs中体现
u8 flags; // 表明一些驱动的feature
void *driver_data; // cpufreq驱动自己的指针,core层不关注
int (*init) (struct cpufreq_policy *policy);
int (*verify) (struct cpufreq_policy *policy);
int (*setpolicy) (struct cpufreq_policy *policy);
int (*target) (struct cpufreq_policy *policy, /* Deprecated */
unsigned int target_freq,
unsigned int relation);
int (*target_index) (struct cpufreq_policy *policy,
unsigned int index);
// 可选的,但一般都会实现。获取指定CPU的当前工作频率
unsigned int (*get) (unsigned int cpu);
struct freq_attr **attr; // 驱动可以定义一些自己需要在sysfs中体现的参数
...
};
注册cpufreq驱动
core层为驱动提供了cpufreq_register_driver()函数来注册cpufreq驱动,cpufreq_unregister_driver()是去注册接口,不再展开。注册过程包含三个关键逻辑:
- 检查drver实例中的参数设置是否正确。
- 保存driver实例到全局变量cpufreq_driver中,相当于完成注册。
- 触发为系统中的CPU设置policy的流程,该流程见下文分析。
static struct subsys_interface cpufreq_interface = {
.name = "cpufreq",
.subsys = &cpu_subsys, // cpu子系统
.add_dev = cpufreq_add_dev, // 向cpufreq子系统添加和移除cpu的回调
.remove_dev = cpufreq_remove_dev,
};
int cpufreq_register_driver(struct cpufreq_driver *driver_data)
{
unsigned long flags;
// 检查驱动的回调实现是否正确
if (!driver_data || !driver_data->verify || !driver_data->init ||
!(driver_data->setpolicy || driver_data->target_index ||
driver_data->target) ||
(driver_data->setpolicy && (driver_data->target_index ||
driver_data->target)) ||
(!!driver_data->get_intermediate != !!driver_data->target_intermediate))
return -EINVAL;
if (driver_data->setpolicy)
driver_data->flags |= CPUFREQ_CONST_LOOPS;
// cpufreq驱动只能注册一个,保存到全局变量cpufreq_driver中
write_lock_irqsave(&cpufreq_driver_lock, flags);
if (cpufreq_driver) {
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
return -EEXIST;
}
cpufreq_driver = driver_data;
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
// 这一步会为系统的所有CPU设置policy
ret = subsys_interface_register(&cpufreq_interface);
// 监听CPU热插拔事件
register_hotcpu_notifier(&cpufreq_cpu_notifier);
}
policy的管理
调频策略用struct cpufreq_policy来描述。系统中每个CPU都必须有一个调频策略,由于可能存在多个CPU共用一个调频策略的情况(如手机上常见的一个cluster中的CPU频率必须保持一致),所以系统中实际的cpufreq_policy对象数量可能少于CPU个数。core层用Per-CPU变量为每个CPU保存对应的cpufreq_policy对象指针。
static DEFINE_PER_CPU(struct cpufreq_policy *, cpufreq_cpu_data);
// 系统中所有的cpufreq_policy对象组织到全局链表中
static LIST_HEAD(cpufreq_policy_list);
struct cpufreq_policy
cpufreq_policy代表一个CPU调频策略,其关键字段如下:
struct cpufreq_policy {
// 共享该policy的CPU掩码,cpus为online状态的CPU集合,related_cpus为所有共享该plocy的CPU集合
cpumask_var_t cpus;
cpumask_var_t related_cpus;
unsigned int cpu; // 每个policy都有一个管理cpu
unsigned int last_cpu; // 由于CPU可以热插拔,保存前一个管理该policy的CPU
// CPU支持的最大、最小等频率信息,由驱动设置
struct cpufreq_cpuinfo cpuinfo;/* see above */
unsigned int min; /* in kHz */
unsigned int max; /* in kHz */
unsigned int cur; /* in kHz, only needed if cpufreq
governors are used */
unsigned int policy; // 硬件自己控制具体的CPU频率时才使用该字段
struct cpufreq_governor *governor; // 软件控制频率时,具体的governor实现
void *governor_data;
bool governor_enabled; /* governor start/stop flag */
// CPU的工作频率并非是可以调节为任意值的,驱动提供了可调节的频率挡位
struct cpufreq_frequency_table *freq_table;
struct list_head policy_list; // 将该policy组织到全局链表中
};
// 所有的policy对象保存到全局链表中
DEFINE_MUTEX(cpufreq_governor_lock);
static LIST_HEAD(cpufreq_policy_list);
设置CPU的policy
在驱动注册的最后,会调用subsys_interface_register()函数遍历系统中所有CPU,将每个CPU以设备的方式通过cpufreq_add_dev()函数添加到core层,这时会为CPU设置调频策略。
static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
{
return __cpufreq_add_dev(dev, sif);
}
static int __cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
{
unsigned int j, cpu = dev->id;
int ret = -ENOMEM;
struct cpufreq_policy *policy;
unsigned long flags;
bool recover_policy = cpufreq_suspended;
if (cpu_is_offline(cpu)) // 只为online的CPU分配调频策略
return 0;
#ifdef CONFIG_SMP
// 可能存在多个CPU共用一个策略的情况,所以如果该CPU已经设置了策略,则处理结束
policy = cpufreq_cpu_get(cpu);
if (unlikely(policy)) {
cpufreq_cpu_put(policy);
return 0;
}
#endif
// 分配一个policy对象
policy = recover_policy ? cpufreq_policy_restore(cpu) : NULL;
if (!policy) {
recover_policy = false;
policy = cpufreq_policy_alloc();
}
// 设置该CPU为policy的管理CPU
if (recover_policy && cpu != policy->cpu)
WARN_ON(update_policy_cpu(policy, cpu, dev));
else
policy->cpu = cpu;
cpumask_copy(policy->cpus, cpumask_of(cpu)); // 当前cpu属于该policy管理
init_completion(&policy->kobj_unregister);
INIT_WORK(&policy->update, handle_update);
// 调用驱动的init回调。驱动会设置policy中的参数
ret = cpufreq_driver->init(policy);
// 驱动在init()回调中必须正确的设置哪些CPU会共享该policy,这样core层才能正确设置policy中的cpus字段
cpumask_or(policy->related_cpus, policy->related_cpus, policy->cpus);
cpumask_and(policy->cpus, policy->cpus, cpu_online_mask);
if (!recover_policy) {
policy->user_policy.min = policy->min;
policy->user_policy.max = policy->max;
}
// 为共用同一个policy的其它CPU设置Per-CPU指针,指向分配的policy对象
write_lock_irqsave(&cpufreq_driver_lock, flags);
for_each_cpu(j, policy->cpus)
per_cpu(cpufreq_cpu_data, j) = policy;
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
// 获取CPU的当前工作频率
if (cpufreq_driver->get && !cpufreq_driver->setpolicy) {
policy->cur = cpufreq_driver->get(policy->cpu);
}
// 对于那种开机时不以可调节频率表中频率运行的情况,这里将CPU的频率调整为一个已知的频率
if ((cpufreq_driver->flags & CPUFREQ_NEED_INITIAL_FREQ_CHECK) && has_target()) {
/* Are we running at unknown frequency ? */
ret = cpufreq_frequency_table_get_index(policy, policy->cur);
if (ret == -EINVAL) {
ret = __cpufreq_driver_target(policy, policy->cur - 1, CPUFREQ_RELATION_L);
}
}
// 发送CPUFREQ_START通知
blocking_notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_START, policy);
if (!recover_policy) {
// 为policy在sysfs中创建属性文件,即/sys/devices/system/cpu/cpufreq/policyX/XXX文件
ret = cpufreq_add_dev_interface(policy, dev);
// 发送CPUFREQ_CREATE_POLICY通知
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
CPUFREQ_CREATE_POLICY, policy);
}
// 将policy对象加入到全局链表中
write_lock_irqsave(&cpufreq_driver_lock, flags);
list_add(&policy->policy_list, &cpufreq_policy_list);
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
// 为policy关联governor
cpufreq_init_policy(policy);
if (!recover_policy) {
policy->user_policy.policy = policy->policy;
policy->user_policy.governor = policy->governor;
}
}
- 只有online状态的cpu才会设置调频策略。
- cpufreq_cpu_get()函数根据Per-CPU变量cpufreq_cpu_data的设置,检查该CPU是否和其它CPU复用了一个policy对象,如果是则不需要进行后续的处理。
- 分配一个policy对象,将当前cpu设置为该policy的管理CPU。然后调用驱动的init()回调,驱动必须在该回调中对policy中的cpu、频率的信息进行正确的设置。
- 继续用驱动设置好的参数设置policy中的其它字段。特别重要的一步是将该共享的policy对象设置到其它CPU的cpufreq_cpu_data中。
- 对外发送通知,并在sysfs中为该policy创建属性节点。
- 将policy加入全局的cpufreq_policy_list链表中。
- 为该policy设置governor,该流程见下文介绍。
governor的管理
policy的调频策略实际由governor实现的,系统可以有多个governor,这些governor注册到core层后,用户态可以通过sysfs节点来为CPU指定具体使用哪个governor。系统用链表保存所有支持的governor。
static LIST_HEAD(cpufreq_governor_list);
core层提供了governor的注册函数cpufreq_register_governor(),实现很简单,不再展开。
struct governor
每个governor都需要实例化一个该对象,然后将其注册到core层。
struct cpufreq_policy {
...
struct cpufreq_governor *governor;
void *governor_data;
}
struct cpufreq_governor {
char name[CPUFREQ_NAME_LEN]; // 每个governor都有一个唯一的名字
int initialized;
// 处理governor的启动和停止事件
int (*governor) (struct cpufreq_policy *policy,
unsigned int event);
ssize_t (*show_setspeed) (struct cpufreq_policy *policy,
char *buf);
int (*store_setspeed) (struct cpufreq_policy *policy,
unsigned int freq);
// governor实现对驱动的约束,要求驱动切换频率的最大时延必须小于该值
unsigned int max_transition_latency;
struct list_head governor_list; // 将governor组织到全局链表中
};
为policy设置governor
policy的调频策略需要由governor来实现,如前面“设置CPU的policy”中分析,CPU设置了policy后,需要为policy关联一个governor。这是通过cpufreq_init_policy()函数完成的。用户态可以通过sysfs修改policy的governor,此时也会触发类似的流程。
// 记录每个CPU的governor,如果不配置则为其选择默认的governor
static DEFINE_PER_CPU(char[CPUFREQ_NAME_LEN], cpufreq_cpu_governor);
static void cpufreq_init_policy(struct cpufreq_policy *policy)
{
struct cpufreq_governor *gov = NULL;
struct cpufreq_policy new_policy; // 借助一个临时变量完成设置过程
int ret = 0;
memcpy(&new_policy, policy, sizeof(*policy));
// 根据名字从全局链表中选择governor,开机时可能尚未配置,选择一个默认的配置,默认配置来自系统config
gov = __find_governor(per_cpu(cpufreq_cpu_governor, policy->cpu));
if (gov)
pr_debug("Restoring governor %s for cpu %d\n", policy->governor->name, policy->cpu);
else
gov = CPUFREQ_DEFAULT_GOVERNOR;
new_policy.governor = gov;
if (cpufreq_driver->setpolicy)
cpufreq_parse_governor(gov->name, &new_policy.policy, NULL);
// 用新的配置更新当前policy
ret = cpufreq_set_policy(policy, &new_policy);
}
// policy : current policy.
new_policy: policy to be set.
static int cpufreq_set_policy(struct cpufreq_policy *policy,
struct cpufreq_policy *new_policy)
{
struct cpufreq_governor *old_gov;
int ret;
memcpy(&new_policy->cpuinfo, &policy->cpuinfo, sizeof(policy->cpuinfo));
// 让驱动校验policy的参数设置是否正确
ret = cpufreq_driver->verify(new_policy);
// 发送CPUFREQ_ADJUST通知
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
CPUFREQ_ADJUST, new_policy);
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
CPUFREQ_INCOMPATIBLE, new_policy);
// 上述的通知过程中可以调整policy,重新校验policy参数设置
ret = cpufreq_driver->verify(new_policy);
/* notification of the new policy */
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
CPUFREQ_NOTIFY, new_policy);
policy->min = new_policy->min;
policy->max = new_policy->max;
if (cpufreq_driver->setpolicy) { // 驱动只需要设置策略的情形
policy->policy = new_policy->policy;
return cpufreq_driver->setpolicy(new_policy);
}
if (new_policy->governor == policy->governor)
goto out;
// 更新governor,对旧的governor执行STOP和EXIT,对新的governor执行INIT和START
old_gov = policy->governor;
if (old_gov) {
__cpufreq_governor(policy, CPUFREQ_GOV_STOP);
up_write(&policy->rwsem);
__cpufreq_governor(policy, CPUFREQ_GOV_POLICY_EXIT);
down_write(&policy->rwsem);
}
policy->governor = new_policy->governor;
if (!__cpufreq_governor(policy, CPUFREQ_GOV_POLICY_INIT)) {
if (!__cpufreq_governor(policy, CPUFREQ_GOV_START))
goto out;
}
}
事件通知
事件通知虽然也是在core层实现的,但是它是相对独立的内容,这里单独对其进行分析。
cpufreq子系统利用Linux标准的通知链机制,实现了两种通知:policy通知和Transition通知。外部模块可以监听这两种通知事件,在事件发生时执行一些需要的逻辑。
// 通知监听接口
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list);
int cpufreq_unregister_notifier(struct notifier_block *nb, unsigned int list);
// list参数代表了要监听的通知类型
#define CPUFREQ_TRANSITION_NOTIFIER (0)
#define CPUFREQ_POLICY_NOTIFIER (1)
static BLOCKING_NOTIFIER_HEAD(cpufreq_policy_notifier_list);
static struct srcu_notifier_head cpufreq_transition_notifier_list;
policy通知
cpufreq子系统在CPU的policy发生变化时,会依次发送三个policy通知事件:
- 首先发出CPUFREQ_ADJUST事件,给通知接收者一个机会来修改policy中的参数,比如温控模块可能会修改其最大频率。
- 然后发出CPUFREQ_INCOMPATIBLE事件,给通知接收者一个机会来检查policy的参数是否和硬件不兼容,如果不兼容可以修改。这两个事件时挨着依次发出的,除了事件含义不同外,效果完全相同,所以事件接收者完全可以在一个事件中将所有该处理的事情执行完毕。
- 最后发出CPUFREQ_NOTIFY事件告诉通知接收者,将以该policy作为最终配置。
/* Policy Notifiers */
#define CPUFREQ_ADJUST (0)
#define CPUFREQ_INCOMPATIBLE (1)
#define CPUFREQ_NOTIFY (2)
// 另外几个事件用来通知policy对象的生命周期变化
#define CPUFREQ_START (3)
#define CPUFREQ_UPDATE_POLICY_CPU (4)
#define CPUFREQ_CREATE_POLICY (5)
#define CPUFREQ_REMOVE_POLICY (6)
Transition通知
cpufreq子系统在CPU的频率发生变化前后,会依次发送两个Transition通知事件,携带的参数表明了修改的CPU及其变化前后的频率。
/* Transition notifiers */
#define CPUFREQ_PRECHANGE (0)
#define CPUFREQ_POSTCHANGE (1)
struct cpufreq_freqs {
unsigned int cpu; /* cpu nr */
unsigned int old;
unsigned int new;
u8 flags; /* flags of cpufreq_driver, see below. */
};
用户态接口
cpufreq子系统在sysfs中有如下接口,通过这些既可以展示一些配置信息,也给用户态提供了一些可调整的参数。
cpufreq子系统的总目录是/sys/devices/system/cpu/cpufreq,下面是系统中所有的policy实例,每个policy目录名中的数字是该policy的管理CPU ID。其中schedutil是一种基于调度器的governor,该目录保存了该governor在sysfs中的内容。
每个policyX目录下是该policy暴露的节点,这些节点大部分是只读的,其含义如下:
- affected_cpus和releated_cpu分别对应内核中policy->cpus和policy->releated_cpus,是该policy管理的CPU掩码。
- cpuinfo_xxx_freq字段表示该CPU可以支持CPU频率,单位为kHZ。
- cpuinfo_transition_latency字段表示该CPU频率切换需要的时延,单位为ns。
- scaling_available_frequencies表示该组CPU可以调整的频率挡位;scaling_available_governors表示该组CPU可以选择的governor;scaling_driver表示当前的驱动名称;scaling_governor表示当前的governor名称。
- scaling_xxx_freq表示当前该CPU上配置的可调节频率信息。
- 用户态可以通过向scaling_setspeed节点写入频率来调整CPU频率,当然内核可能会不支持该操作。
每个CPU都必须有一种policy,所以在cpu的目录下会有一个软链接指向对应的policy目录:
参考资料
- cpufreq schedutil原理剖析-CSDN博客;
- https://www.cnblogs.com/LoyenWang/p/11385811.html;
- 内核Doc:Documentation/cpu-freq/core.txt;