从基本理解到深入探究 Linux动态频率调节系统cpufreq
Posted Wu_Being
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从基本理解到深入探究 Linux动态频率调节系统cpufreq相关的知识,希望对你有一定的参考价值。
文章目录
概述
随着人民日益增长的美好生活需要,人民对美好生活的向往,我们对芯片的处理能力提出了越来越高的需求,芯片厂家也对制造工艺不断地提升。现在的高端智能手机的中央处理器已经可以工作在2.8GHz以上,而主流的PC处理器的主频能达到5GHz左右,可是我们并不是想让CPU时时刻刻都工作在最高的主频上,尤其是移动设备和笔记本电脑,大部分时间里,CPU其实工作在轻负载状态下,由文章《集成电路的功率和能耗》我们知道:主频越高,功耗也越高。为了节省CPU的功耗和减少发热,我们有必要根据当前CPU的负载状态,动态地提供刚好足够的主频给CPU。在Linux中,内核的开发者定义了一套框架模型来完成这一目的,它就是CPUFreq系统。
1. 管中窥豹——sysfs接口
我们先从CPUFreq提供的sysfs接口入手,先在kernel 外面划来划去直观地看看它提供了那些功能再深入刨根问底源码。以下是我的adb shell 输出的结果(输出粗体是高通他家特有的,斜体是其他家特有的):
android:/sys/devices/system/cpu $ ls
core_ctl_isolated cpu4 cpuidle kernel_max power
cpu0 cpu5 hang_detect_gold modalias present
cpu1 cpu6 hang_detect_silver offline uevent
cpu2 cpu7 hotplug online
cpu3 cpufreq isolated possible probe
android:/sys/devices/system/cpu $ ls -l
-r–r--r-- 1 root root 4096 1970-01-01 00:06 core_ctl_isolated
drwxr-xr-x 9 root root 0 1970-01-01 00:00 cpu0
drwxr-xr-x 8 root root 0 1970-01-01 00:00 cpu1
drwxr-xr-x 8 root root 0 1970-01-01 00:00 cpu2
drwxr-xr-x 8 root root 0 1970-01-01 00:00 cpu3
drwxr-xr-x 8 root root 0 1970-01-01 00:00 cpu4
drwxr-xr-x 8 root root 0 1970-01-01 00:00 cpu5
drwxr-xr-x 8 root root 0 1970-01-01 00:00 cpu6
drwxr-xr-x 8 root root 0 1970-01-01 00:00 cpu7
drwxr-xr-x 4 root root 0 1970-01-01 00:00 cpufreq
drwxr-xr-x 2 root root 0 1970-01-01 00:00 cpuidle
drwxr-xr-x 2 root root 0 1970-01-01 00:00 hang_detect_gold
drwxr-xr-x 2 root root 0 1970-01-01 00:00 hang_detect_silver
drwxr-xr-x 2 root root 0 1970-01-01 00:00 hotplug
-r–r--r-- 1 root root 4096 1970-01-01 00:06 isolated
-r–r--r-- 1 root root 4096 1970-01-01 00:06 kernel_max
-r–r--r-- 1 root root 4096 1970-01-01 00:06 modalias
-r–r--r-- 1 root root 4096 1970-01-01 00:06 offline
-r–r--r-- 1 root root 4096 1970-01-01 00:00 online
-r–r--r-- 1 root root 4096 1970-01-01 00:06 possible
drwxr-xr-x 2 root root 0 1970-01-01 00:00 power
-r–r--r-- 1 root root 4096 1970-01-01 00:06 present
-rw-r–r-- 1 root root 4096 1970-01-01 00:00 uevent
所有与CPUFreq相关的sysfs接口都位于:/sys/devices/system/cpu
下面,我们可以看到,8个cpu分别建立了一个自己的目录,从cpu0到cpu7,我们再看看offline和online以及present的内容:
- online 代表目前正在工作的cpu,输出显示编号为0-7这8个cpu在工作,
- offline 代表目前被关掉的cpu,
- present 表示主板上已经安装的cpu,
android:/sys/devices/system/cpu $ cat online
0-7
android:/sys/devices/system/cpu $ cat offline
android:/sys/devices/system/cpu $ cat present
0-7
android:/sys/devices/system/cpu $ cat kernel_max
7
如果使用了类似intel的超线程技术,我们能看到输出kernel_max是15(即16个CPU,其下标0开始),第8-15号cpu处于关闭状态(实际上不存在,因为present只有0-7),其实物理上只是8个。
接着往下看:
android:/sys/devices/system/cpu/cpu0 $ ls
cache cpuidle isolate online regs sched_load_boost topology
cpufreq hotplug of_node power rq-stats subsystem uevent
crash_notes node0 microcode thermal_throttle
android:/sys/devices/system/cpu/cpu0 $ ls -l
drwxr-xr-x 6 root root 0 1970-01-01 00:00 cache
lrwxrwxrwx 1 root root 0 1970-01-01 00:00 cpufreq -> …/cpufreq/policy0
drwxr-xr-x 5 root root 0 1970-01-01 00:00 cpuidle
drwxr-xr-x 2 root root 0 1970-01-01 00:00 hotplug
-r–r--r-- 1 root root 4096 1970-01-01 00:00 isolate
lrwxrwxrwx 1 root root 0 1970-01-01 00:35 of_node -> …/…/…/…/firmware/devicetree/base/cpus/cpu@0
-rw-r–r-- 1 root root 4096 1970-01-01 00:00 online
drwxr-xr-x 2 root root 0 1970-01-01 00:00 power
drwxr-xr-x 3 root root 0 1970-01-01 00:00 regs
drwxr-xr-x 2 root root 0 1970-01-01 00:00 rq-stats
-rw-r–r-- 1 root root 4096 1970-01-01 00:00 sched_load_boost
lrwxrwxrwx 1 root root 0 1970-01-01 00:35 subsystem -> …/…/…/…/bus/cpu
drwxr-xr-x 2 root root 0 1970-01-01 00:00 topology
-rw-r–r-- 1 root root 4096 1970-01-01 00:00 uevent
android:/sys/devices/system/cpu/cpu0/cpufreq $ ls
affected_cpus related_cpus scaling_governor
cpuinfo_cur_freq scaling_available_frequencies scaling_max_freq
cpuinfo_max_freq scaling_available_governors scaling_min_freq
cpuinfo_min_freq scaling_cur_freq scaling_setspeed
cpuinfo_transition_latency scaling_driver bios_limit stats
android:/sys/devices/system/cpu/cpu0/cpufreq $ ls -l
-r–r--r-- 1 root root 4096 1970-01-01 00:35 affected_cpus
-r-------- 1 root root 4096 1970-01-01 00:35 cpuinfo_cur_freq
-r–r--r-- 1 root root 4096 1970-01-01 00:00 cpuinfo_max_freq
-r–r--r-- 1 root root 4096 1970-01-01 00:35 cpuinfo_min_freq
-r–r--r-- 1 root root 4096 1970-01-01 00:35 cpuinfo_transition_latency
-r–r--r-- 1 root root 4096 1970-01-01 00:35 related_cpus
-r–r--r-- 1 root root 4096 1970-01-01 00:00 scaling_available_frequencies
-r–r--r-- 1 root root 4096 1970-01-01 00:35 scaling_available_governors
-r–r--r-- 1 root root 4096 1970-01-01 00:35 scaling_cur_freq
-r–r--r-- 1 root root 4096 1970-01-01 00:35 scaling_driver
-rw-r–r-- 1 root root 4096 1970-01-01 00:00 scaling_governor
-rw-rw---- 1 system system 4096 1970-01-01 00:00 scaling_max_freq
-rw-rw-r-- 1 system system 4096 1970-01-01 00:00 scaling_min_freq
-rw-r–r-- 1 root root 4096 1970-01-01 00:35 scaling_setspeed
android:/sys/devices/system/cpu/cpu0/cpufreq $
- 前缀cpuinfo 代表的是cpu硬件上支持的频率
- 前缀scaling 代表的是可以通过CPUFreq系统用软件进行调节时所支持的频率
- cpuinfo_cur_freq 代表通过硬件实际上读到的频率值,而scaling_cur_freq则是软件当前的设置值,多数情况下这两个值是一致的,但是也有可能因为硬件的原因,有微小的差异.
比如下面数据,我的cpu0的最低运行频率是0.6GHz,最高是1.8GHz,目前正在运行的频率是0.6GHz。
cpuinfo_cur_freq: 652800
cpuinfo_max_freq: 1804800
cpuinfo_min_freq: 652800
scaling_cur_freq: 652800
scaling_max_freq: 1804800
scaling_min_freq: 652800
- scaling_available_frequencies 会输出当前软件支持的频率值,下面输出,从0.6GHz到1.8GHz,一共支持5挡的频率可供选择。
android:/sys/devices/system/cpu/cpufreq/policy0 # cat scaling_available_fre*
cat scaling_available_fre*
652800 1036800 1401600 1689600 1804800
- scaling_available_governors 则会输出当前可供选择的频率调节策略
android:/sys/devices/system/cpu/cpufreq/policy0 # cat scaling_available_governors
conservative ondemand userspace powersave performance schedutil
- scaling_governor 一共有6中策略供我们选择,那么当前系统选用那种策略?让我们看看:
android:/sys/devices/system/cpu/cpu0/cpufreq # cat scaling_governor
schedutil
OK,我的系统当前选择schedutil这种策略,这是高通自加的策略,详细的情况我们留在后面的章节中讨论。
- ondemand: 只要cpu的负载超过某一个阀值,cpu的频率会立刻提升至最高,然后再根据实际情况降到合适的水平。
- userspace:我们可以通过scaling_setspeed手工设置需要的频率
- powersave:简单地使用最低的工作频率进行运行
- performance:一直选择最高的频率进行运行
- scaling_driver 则会输出当前使用哪一个驱动来设置cpu的工作频率。
android:/sys/devices/system/cpu/cpu0/cpufreq # cat scaling_driv
msm
2. 刨根问底——软件架构
通过上一节的介绍,我们可以大致梳理出CPUFreq系统的构成和工作方式。
- 首先,CPU的硬件特性决定了这个CPU的最高和最低工作频率,所有的频率调整数值都必须在这个范围内,它们用cpuinfo_xxx_freq来表示。
- 然后,我们可以在这个范围内再次定义出一个软件的调节范围,它们用scaling_xxx_freq来表示。
- 同时,根据具体的硬件平台的不同,我们还需要提供一个频率表,这个频率表规定了cpu可以工作的频率值,当然这些频率值必须要在cpuinfo_xxx_freq的范围内。
有了这些频率信息,CPUFreq系统就可以根据当前cpu的负载轻重状况,合理地从频率表中选择一个合适的频率供cpu使用,已达到节能的目的。至于如何选择频率表中的频率,这个要由不同的governor来实现,目前的原生的内核版本提供了5种governor供我们选择。选择好适当的频率以后,具体的频率调节工作就交由scaling_driver来完成。
CPUFreq系统把一些公共的逻辑和接口代码抽象出来,这些代码与平台无关,也与具体的调频策略无关,内核的文档把它称为CPUFreq Core(/Documents/cpufreq/core.txt
)。另外一部分,与实际的调频策略相关的部分被称作cpufreq_policy,cpufreq_policy又是由频率信息和具体的governor组成,governor才是具体策略的实现者,当然governor需要我们提供必要的频率信息,governor的实现最好能做到平台无关,与平台相关的代码用cpufreq_driver表述,它完成实际的频率调节工作。最后,如果其他内核模块需要在频率调节的过程中得到通知消息,则可以通过cpufreq notifiers来完成。由此,我们可以总结出CPUFreq系统的软件结构如下:
1. cpufreq_policy
一种调频策略governor的各种限制条件的组合称之为policy,代码中用cpufreq_policy这一数据结构来表示:
struct cpufreq_policy
cpumask_var_t cpus;
cpumask_var_t related_cpus;
unsigned int shared_type;
unsigned int cpu;
unsigned int last_cpu;
struct cpufreq_cpuinfo cpuinfo;
unsigned int min; /* in kHz */
unsigned int max; /* in kHz */
unsigned int cur;
unsigned int policy;
struct cpufreq_governor *governor;
void *governor_data;
struct work_struct update;
struct cpufreq_real_policy user_policy;
struct kobject kobj;
struct completion kobj_unregister;
;
其中的各个字段的解释如下:
- cpus和related_cpus 这两个都是cpumask_var_t变量,cpus表示的是这一policy控制之下的所有还出于online状态的cpu,而related_cpus则是online和offline两者的合集。主要是用于多个cpu使用同一种policy的情况,实际上,我们平常见到的大多数系统中都是这种情况:所有的cpu同时使用同一种policy。我们需要related_cpus变量指出这个policy所管理的所有cpu编号。
- cpu和last_cpu 虽然一种policy可以同时用于多个cpu,但是通常一种policy只会由其中的一个cpu进行管理,cpu变量用于记录用于管理该policy的cpu编号,而last_cpu则是上一次管理该policy的cpu编号(因为管理policy的cpu可能会被plug out,这时候就要把管理工作迁移到另一个cpu上)。
- cpuinfo 保存cpu硬件所能支持的最大和最小的频率以及切换延迟信息。
min/max/cur 该policy下的可使用的最小频率,最大频率和当前频率。 - policy 该变量可以取以下两个值:CPUFREQ_POLICY_POWERSAVE和CPUFREQ_POLICY_PERFORMANCE,该变量只有当调频驱动支持setpolicy回调函数的时候有效,这时候由驱动根据policy变量的值来决定系统的工作频率或状态。如果调频驱动(cpufreq_driver)支持target回调,则频率由相应的governor来决定。
- governor和governor_data 指向该policy当前使用的cpufreq_governor结构和它的上下文数据。governor是实现该policy的关键所在,调频策略的逻辑由governor实现。
- update 有时在中断上下文中需要更新policy,需要利用该工作队列把实际的工作移到稍后的进程上下文中执行。
- user_policy 有时候因为特殊的原因需要修改policy的参数,比如溫度过高时,最大可允许的运行频率可能会被降低,为了在适当的时候恢复原有的运行参数,需要使用user_policy保存原始的参数(min,max,policy,governor)。
- kobj 该policy在sysfs中对应的kobj的对象。
2. cpufreq_governor
所谓的governor,我把它翻译成:调节器。governor负责检测cpu的使用状况,从而在可用的范围中选择一个合适的频率,代码中它用cpufreq_governor结构来表示:
struct cpufreq_governor
char name[CPUFREQ_NAME_LEN];
int initialized;
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);
unsigned int max_transition_latency; /* HW must be able to switch to
next freq faster than this value in nano secs or we
will fallback to performance governor */
struct list_head governor_list;
struct module *owner;
;
其中的各个字段的解释如下:
- name 该governor的名字。
- initialized 初始化标志。
- governor 指向一个回调函数,CPUFreq Core会在不同的阶段调用该回调函数,用于该governor的启动、停止、初始化、退出动作。
- list_head 所有注册的governor都会利用该字段链接在一个全局链表中,以供系统查询和使用。
3. cpufreq_driver
上一节提到的gonvernor只是负责计算并提出合适的频率,但是频率的设定工作是平台相关的,这需要cpufreq_driver驱动来完成,cpufreq_driver的结构如下:
struct cpufreq_driver
struct module *owner;
char name[CPUFREQ_NAME_LEN];
u8 flags;
bool have_governor_per_policy;
/* needed by all drivers */
int (*init) (struct cpufreq_policy *policy);
int (*verify) (struct cpufreq_policy *policy);
/* define one out of two */
int (*setpolicy) (struct cpufreq_policy *policy);
int (*target) (struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation);
/* should be defined, if possible */
unsigned int (*get) (unsigned int cpu);
/* optional */
unsigned int (*getavg) (struct cpufreq_policy *policy,
unsigned int cpu);
int (*bios_limit) (int cpu, unsigned int *limit);
int (*exit) (struct cpufreq_policy *policy);
int (*suspend) (struct cpufreq_policy *policy);
int (*resume) (struct cpufreq_policy *policy);
struct freq_attr **attr;
;
相关的字段的意义解释如下:
- name 该频率驱动的名字。
- init 回调函数,该回调函数必须实现,CPUFreq Core会通过该回调函数对该驱动进行必要的初始化工作。
- verify 回调函数,该回调函数必须实现,CPUFreq Core会通过该回调函数检查policy的参数是否被驱动支持。
- setpolicy/target 回调函数,驱动必须实现这两个函数中的其中一个,如果不支持通过governor选择合适的运行频率,则实现setpolicy回调函数,这样系统只能支持CPUFREQ_POLICY_POWERSAVE和CPUFREQ_POLICY_PERFORMANCE这两种工作策略。反之,实现target回调函数,通过target回调设定governor所需要的频率。
- get 回调函数,用于获取cpu当前的工作频率。
- getavg 回调函数,用于获取cpu当前的平均工作频率。
4. cpufreq notifiers
CPUFreq的通知系统使用了内核的标准通知接口。它对外提供了两个通知事件:policy通知和transition通知。
policy通知用于通知其它模块cpu的policy需要改变,每次policy改变时,该通知链上的回调将会用不同的事件参数被调用3次,分别是:
- CPUFREQ_ADJUST 只要有需要,所有的被通知者可以在此时修改policy的限制信息,比如温控系统可能会修改在大允许运行的频率。
- CPUFREQ_INCOMPATIBLE 只是为了避免硬件错误的情况下,可以在该通知中修改policy的限制信息。
- CPUFREQ_NOTIFY 真正切换policy前,该通知会发往所有的被通知者。
transition通知链用于在驱动实施调整cpu的频率时,用于通知相关的注册者。每次调整频率时,该通知会发出两次通知事件:
- CPUFREQ_PRECHANGE 调整前的通知。
- CPUFREQ_POSTCHANGE 完成调整后的通知。
- CPUFREQ_RESUMECHANGE 当检测到因系统进入suspend而造成频率被改变时,该通知消息会被发出
核心(core)架构与API
上一面中,我们大致地讲解了一下CPUFreq在用户空间的sysfs接口和它的几个重要的数据结构,同时也提到,CPUFreq子系统把一些公共的代码逻辑组织在一起,构成了CPUFreq的核心部分,这些公共逻辑向CPUFreq和其它内核模块提供了必要的API,像cpufreq_governor、cpufreq_driver等模块通过这些API来完成一个完整的CPUFreq体系。这一节我们就来讨论一下核心架构的代码架构以及如何使用这些公共的API接口。
核心部分的代码都在:/drivers/cpufreq/cpufreq.c中,本系列文章我使用的内核版本是3.10.0.
1. CPUFreq子系统的初始化
先看看具体的代码:
static int __init cpufreq_core_init(void)
int cpu;
if (pufreq_disabled())
return -ENODEV;
for_each_possible_cpu(cpu)
per_cpu(cpufreq_policy_cpu, cpu) = -1;
init_rwsem(&per_cpu(cpu_policy_rwsem, cpu));
cpufreq_global_kobject = kobject_create_and_add("cpufreq", &cpu_subsys.dev_root->kobj);
BUG_ON(!cpufreq_global_kobject);
register_syscore_ops(&cpufreq_syscore_ops);
return 0;
core_initcall(cpufreq_core_init);
可见,在系统的启动阶段,经由initcall机制,cpufreq_core_init被调用,由它来完成核心部分的初始化工作,其中:
cpufreq_policy_cpu 是一个per_cpu变量,在smp的系统下,每个cpu可以有自己独立的调频policy,也可以所有的cpu都是用一种policy,这时候就有可能出现其中一个cpu管理着某个policy,而其它cpu因为也使用同一个policy,这些cpu的policy的就交由那个管理cpu代管,这个per_cpu变量就是用来记录各个cpu的policy实际上是由那个cpu进行管理的。初始化时都被初始化为-1了,代表现在还没有开始进行policy的管理。
接下来的kobject_create_and_add函数在/sys/devices/system/cpu这个节点下建立了一个cpufreq节点,该节点的下面以后会用来放置当前governor的一些配置参数。参数cpu_subsys是内核的一个全局变量,是由更早期的初始化时初始化的,代码在drivers/base/cpu.c中:
struct bus_type cpu_subsys =
.name = "cpu",
.dev_name = "cpu",
;
EXPORT_SYMBOL_GPL(cpu_subsys);
void __init cpu_dev_init(void)
if (subsys_system_register(&cpu_subsys, cpu_root_attr_groups))
panic("Failed to register CPU subsystem");
cpu_dev_register_generic();
这将会建立一根cpu总线,总线下挂着系统中所有的cpu,cpu总线设备的根目录就位于:/sys/devices/system/cpu,同时,/sys/bus下也会出现一个cpu的总线节点。cpu总线设备的根目录下会依次出现cpu0,cpu1,… cpux节点,每个cpu对应其中的一个设备节点。CPUFreq子系统利用这个cpu_subsys来获取系统中的cpu设备,并在这些cpu设备下面建立相应的cpufreq对象,这个我们在后面再讨论。
这样看来,cpufreq子系统的初始化其实没有做什么重要的事情,只是初始化了几个per_cpu变量和建立了一个cpufreq文件节点。下图是初始化过程的序列图:
图 1.1 核心层初始化
2. 注册cpufreq_governor
系统中可以同时存在多个governor策略,一个policy通过cpufreq_policy结构中的governor指针和某个governor相关联。要想一个governor被policy使用,首先要把该governor注册到cpufreq的核心中,我们可以通过核心层提供的API来完成注册:
int cpufreq_register_governor(struct cpufreq_governor *governor)
int err;
......
governor->initialized = 0;
err = -EBUSY;
if (__find_governor(governor->name) == NULL)
err = 0;
list_add(&governor->governor_list, &cpufreq_governor_list);
......
return err;
核心层定义了一个全局链表变量:cpufreq_governor_list,注册函数首先根据governor的名称,通过__find_governor()函数查找该governor是否已經被注册过,如果没有被注册过,则把代表该governor的结构体添加到cpufreq_governor_list链表中。在上一篇中我们提到,目前的内核版本提供了5种governor供我们使用,我们可以通过内核的配置項来选择需要编译的governor,同时需要指定一个默认的governor。在cpufreq.h中,将会根据配置項的选择,把CPUFREQ_DEFAULT_GOVERNOR宏指向默认governor结构体变量的地址,在注册cpufreq_driver的阶段需要使用这个宏来设定系统默认使用的governor。
3. 注册一个cpufreq_driver驱动
与governor不同,系统中只会存在一个cpufreq_driver驱动,根据上一面Linux动态频率调节系统CPUFreq之一:概述的介绍,cpufreq_driver是平台相关的,负责最终实施频率的调整动作,而选择工作频率的策略是由governor完成的。所以,系统中只需要注册一个cpufreq_driver即可,它只负责知道如何控制该平台的时钟系统,从而设定由governor确定的工作频率。注册cpufreq_driver驱动会触发cpufreq核心的一系列额外的初始化动作,第一节所说的核心初始化工作非常简单,实际上,更多的初始化动作在注册cpufreq_driver阶段完成。核心提供了一个API:cpufreq_register_driver来完成注册工作。下面我们分析一下这个函数的工作过程:
int cpufreq_register_driver(struct cpufreq_driver *driver_data)
......
if (cpufreq_disabled())
return -ENODEV;
if (!driver_data || !driver_data->verify || !driver_data->init ||
((!driver_data->setpolicy) && (!driver_data->target)))
return -EINVAL;
该API只有一个参数:一个cpufreq_driver指针,driver_data,该结构事先在驱动的代码中定义,调用该API时作为参数传入。函数先判断系统目前是否禁止了调频功能,然后检查cpufreq_driver的几个回调函数是否被实现,由代码可以看出,verify和init回调函数必须要实现,而setpolicy和target回调则至少要被实现其中的一个。这几个回调的作用请参考本系列的第一篇文章。接下来:
write_lock_irqsave(&cpufreq_driver_lock, flags);
if (cpufreq_driver)
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
return -EBUSY;
cpufreq_driver = driver_data;
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
检查全局变量cpufreq_driver是否已经被赋值,如果没有,则传入的参数被赋值给全局变量cpufreq_driver,从而保证了系统中只会注册一个cpufreq_driver驱动。然后:
ret = subsys_interface_register(&cpufreq_interface);
......
......
register_hotcpu_notifier(&cpufreq_cpu_notifier);
通过subsys_interface_register给每一个cpu建立一个cpufreq_policy,最后注册cpu hot plug通知,以便在cpu hot plug的时候,能够动态地处理各个cpu policy之间的关系(比如迁移负责管理的cpu等等)。这里要重点讨论一下subsys_interface_register的过程,回到第一节的内容,我们知道初始化阶段,cpu_subsys被建立,从而每个cpu都会在cpu总线设备下建立一个属于自己的设备:sys/devices/system/cpu/cpux。subsys_interface_register负责在cpu_subsys子系统的子设备下面注册公共的接口。我们看看参数cpufreq_interface的定义:
static struct subsys_interface cpufreq_interface =
.name = "cpufreq",
.subsys = &cpu_subsys,
.add_dev = cpufreq_add_dev,
.remove_dev = cpufreq_remove_dev,
;
subsys_interface_register函数的代码我就不再展开了,它的大致作用就是:遍历子系统下面的每一个子设备,然后用这个子设备作为参数,调用cpufrq_interface结构的add_dev回调函数,这里的回调函数被指向了cpufreq_add_dev,它的具体工作方式我们在下一节中讨论。
driver注册完成后,驱动被保存在全局变量cpufreq_driver中,供核心层使用,同时,每个cpu也会建立自己的policy策略,governor也开始工作,实时地监控着cpu的负载并计算合适的工作频率,然后通过driver调整真正的工作频率。下图是cpufreq_driver注册过程的序列图:
图 3.1 cpufreq_driver的注册过程
4. 为每个cpu建立频率调整策略(policy)
为每个cpu建立频率调整策略实在注册cpufreq_driver阶段的subsys_interface_registe函数中完成的,上一节已经提到,该函数最终会调用cpufreq_add_dev回调函数,现在展开这个函数分析一下:
因为subsys_interface_registe会枚举各个cpu设备,不管该cpu处于offline还是online状态,cpufreq_add_dev都会被调用,所以函数的一开始,判断如果cpu处于offline状态,直接返回。
static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
......
if (cpu_is_offline(cpu))
return 0;
如果是smp系统,本cpu的policy可能和其他cpu共同使用同一个policy,并委托另一个叫做管理cpu的cpu进行管理,下面的代码判断这种情况,如果已经委托别的cpu管理,则直接返回,核心层定义了另一个per_cpu变量:cpufreq_cpu_data,用来保存各个cpu所使用的cpufreq_policy结构的指针,cpufreq_cpu_get函数实际上就是通过这个per_cpu变量,获取该指针,如果该指针非0,代表该cpu已经建立好了它自身的policy(可能是在他之前的管理cpu建立policy期间一并建立的)。
policy = cpufreq_cpu_get(cpu);
if (unlikely(policy))
cpufreq_cpu_put(policy);
return 0;
因为cpu hot plug期间,cpufreq_add_dev也会被调用,下面的代码片段检测该cpu之前是否被hot-unpluged过,如果是,找到其中一个相关的cpu(这些相关的cpu都委托给同一个托管它cpu进行管理,调用cpufreq_add_policy_cpu函数,该函数只是简单地建立一个cpufreq链接,链接到管理cpu的cpufreq节点。
for_each_online_cpu(sibling)
struct cpufreq_policy *cp = per_cpu(cpufreq_cpu_data, sibling);
if (cp && cpumask_test_cpu(cpu, cp->related_cpus))
read_unlock_irqrestore(&cpufreq_driver_lock, flags);
return cpufreq_add_policy_cpu(cpu, sibling, dev);
当这是系统初始化阶段第一次调用cpufreq_add_dev时(subsys_interface_register枚举到的第一个cpu,通常就是cpu0),cpufreq_cpu_data应该为NULL,所以我们要为这样的cpu分配一个cpufreq_policy结构,并初始化该policy所管理的cpu,包括online的cpus字段和online+offline的cpu_related字段,并把自己设置为这个policy的管理cpu,使用默认governor初始化policy->governor字段,同时吧自己加入到online的cpus字段中:
policy = kzalloc(sizeof(struct cpufreq_policy), GFP_KERNEL);
if (!policy)
goto nomem_out;
if (!alloc_cpumask_var(&policy->cpus, GFP_KERNEL))
goto err_free_policy;
if (!zalloc_cpumask_var(&policy->related_cpus, GFP_KERNEL))
goto err_free_cpumask;
policy->cpu = cpu;
policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
cpumask_copy(policy->cpus, cpumask_of(cpu));
/* Initially set CPU itself as the policy_cpu */
per_cpu(cpufreq_policy_cpu, cpu) = cpu;
接下来初始化一个供kobject系统注销时使用的同步变量,初始化一个workqueue,某些时候不能马上执行对该policy的更新操作,可以使用该workqueue来延迟执行。
init_completion(&policy->kobj_unregister);
INIT_WORK(&policy->update, handle_update);
接着,调用cpufreq_driver的init回调,进一步初始化该policy:
ret = cpufreq_driver->init(policy);
if (ret)
pr_debug("initialization failed\\n");
goto err_set_policy_cpu;
在上述驱动的初始化内部,应该完成以下工作:
- 设定该cpu的最大和最小工作频率
- 设定该policy的最大和最小工作频率
- 设定该policy可供调节的频率档位
- 设定cpu调节频率时的延迟时间特性
该policy可以管理的cpu个数(policy->cpus)
继续:
/* related cpus should atleast have policy->cpus */
cpumask_or(policy->related_cpus, policy->related_cpus, policy->cpus);
注释已经写的很清楚了,把online的cpu加到代表online+offline的related字段中。接着,剔除offline的cpu:
cpumask_and(policy->cpus, policy->cpus, cpu_online_mask);
然后,发出CPUFREQ_START通知:
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
CPUFREQ_START, policy);
如果是hot-plug加入的cpu,找出它上次使用的governor:
#ifdef CONFIG_HOTPLUG_CPU
gov = __find_governor(per_cpu(cpufreq_cpu_governor, cpu));
if (gov)
policy->governor = gov;
pr_debug("Restoring governor %s for cpu %d\\n",
policy->governor->name, cpu);
#endif
最后,建立cpu设备下的sysfs文件节点:cpufreq,它的完整路径是:/sys/devices/system/cpu/cpux/cpufreq,同时,在他的下面,相应的sysfs节点也同时被建立,节点的内容请参考本系列的第一篇文章:Linux动态频率调节系统CPUFreq之一:概述:
ret = cpufreq_add_dev_interface(cpu, policy, dev);
至此,一个cpu的policy建立完成,它的频率限制条件、使用的governor策略,sysfs文件节点都已经建立完成。需要注意点是,系统中有多少个cpu,cpufreq_add_dev函数就会被调用多少次,最后,每个cpu都会建立自己的policy,当然,也有可能只有部分cpu建立了真正的policy,而其它cpu则委托这些cpu进行policy的管理,关于这一点,一开始读代码的时候可能有点困扰,为了搞清楚他们之间的关系,我们再跟入cpufreq_add_dev_interface函数看看:
static int cpufreq_add_dev_interface(unsigned int cpu,
struct cpufreq_policy *policy,
struct device *dev)
以上是关于从基本理解到深入探究 Linux动态频率调节系统cpufreq的主要内容,如果未能解决你的问题,请参考以下文章
Linux(debian7)操作基础之CPU频率调整 Linux系统CPU频率调整工具使用
iOS runtime探究: 从runtime開始深入理解OC消息转发机制
R语言绘图(ggplot2、ggpurb)从入门到精通06--柱状图美化之宽度调节
Linux深入理解TCP协议(connectbindlistenaccept)及其源码