---------------
Governor
Driver
Core
Interface to other parts of the kernel
======================================================================
#tag#03-14-2012
ref:
http://blog.csdn.net/guoshaobei/article/details/6090359
1. Cpufreq由来
主流CPU对变频(frequency scaling)技术的支持:
Intel处理器 Enhanced SpeedStep,
AMD PowerNow
...
超频: 是指通过提高核心电压等手段让处理器工作在非标准频率下的行为,这往往会造
成 CPU 使用寿命缩短以及系统稳定性下降等严重后果。
变频技术:是指cpu硬件本身在不同频率下运行,根据变化的系统负载状况切换运行频
率,从而达到性能和功耗二者兼顾的目的。
为了方便维护和降低开发成本导致了cpufreq内核子系统的诞生。
整个过程分为两步:1> “做什么“ //mechanism
根据系统负载的动态变化选择CPU合适的运行频率。
2> “怎么做"//policy
按照选择的运行频率设置CPU
Cpufreq的设计和使用
图1:Cpufreq_arch.jpg
+ Cpufreq 模块:mechanism和policy的隔离//代码重用,方便维护和开发
+ 特定于硬件的driver
+ 选择目标频率的决策者
$ ls -F /sys/devices/system/cpu/cpu0/cpufreq/
$ cpufreq-info
Ondemand governor 的由来及其实现:
总共有五种governors可用,userspace, conservative, ondemand, powersave
和performance。
使用 performance governor 体现的是对系统高性能的最大追求,而使用 powersave governor 则是对系统低功耗的最大追求。
使用 userspace governor 时,系统将变频策略的决策权交给了用户态应用程序,并提供了相应的接口供用户态应用程序调节 CPU 运行频率使用。
在使用 userspace governor 时,系统将变频策略的决策权交给了用户态应
用程序。该用户态应用程序一般是一个 daemon 程序,每隔一定的时间间隔收集一次
系统信息并根据系统的负载情况使用 userspace governor 提供的
scaling_setspeed 接口动态调整 CPU 的运行频率。作为这个 daemon 程序,当时
在几个主要的 Linux 发行版中使用的一般是 powersaved 或者 cpuspeed。这两个
daemon 程序一般每隔几秒钟统计一次 CPU 在这个采样周期内的负载情况,并根据
统计结果调整 CPU 的运行频率。
问题:
+ 性能方面的问题
- 采样周期
- 采样准确性
- 内核态和用户态数据交换
- 频率设置耗时// 250us -> 10us *
* CPU硬件技术的发展为解决上面的问题提供了契机。
ondemand governor完全在内核空间实现,更加细粒度的采样间隔。
Ondemand 降频更加激进, conservative 降频比较缓慢保守,事实使用
ondemand 的效果也是比较好的。
如何实现:
核心任务
- 系统方面:cpu,时钟子系统,sdram等等
static struct cpufreq_driver XXX_driver = {...};
- 受频率变化影响的其他内核模块
系统在变化 cpu 主频的时候会调用 cpufreq_notify_transition(&freqs,
CPUFREQ_POSTCHANGE); 函数,响挂载在这个 通知链上所有的驱动发
出一个信号,驱动接收到这个信号则调用相应的处理函数。
测试://ref: http://blog.csdn.net/linweig/article/details/5972312
------------------------------
ref:linux-loongson-3.0.4,git://dev.lemote.com/linux_loongson.git
Documentation/cpu-freq
CPUFreq core
============
drivers/cpufreq/cpufreq.c
CF核心的作用:
为CF驱动和内核中需要“看到”频率改变的部分代码*之间提供接口。
*: ACPI,TIME, LCD(speed limits)
另外,loops_per_jiffy在系统频率改变后会被更新。
为了cf 驱动的正确管理(注册,卸载。。。),计数维护:
cpufreq_get_cpu() & cpufreq_put_cpu()
CF通知链(CPUFreq notifiers):
有两种类型的CF通知链:策略改变通知链(policy notifiers)和开始改变通知链
(transition notifiers)
> 策略改变通知链(policy notifiers)
该通知链在要设置一个新策略时被调用。该通知链将会被调用三次。
1> CPUFREQ_ADJUST:
在该状态下,所有的CF通知链上的被通告者会在需要时改变频率的界限。
2> CPUFREQ_INCOMPATIBLE:
做一些避免硬件失败的改变
3> CPUFREQ_NOTIFY:
把新策略告知所有的注册的被通告对象
如果在该阶段之前硬件驱动没有在新策略上达成一致,则这些不兼容的硬件将会被停止,
然后内核通知用户。
上述的状态信息是作为第二个参数传递给被通告者的。
第三个参数是一个指向struct cpufreq_policy信息的结构体的实例。
成员:cpu, min, max, policy 和 max_cpu_freq。
cpu:指定了受影响的CPU号
min: max:频率的上下界限。
policy:新的策略(Performance, Ondemand ...)
max_cpu_freq: cpu能够支持的最大频率
> CPU频率改变通知链 (CPUFreq transition notifiers)
当CPUFreq驱动切换CPU核心频率时会给出两次通知。
第二个参数指定了状态,CPUFREQ_PRECHANGE或者CPUFREQ_POSTCHANGE.
第三个参数是一个struct cpufreq_freqs有如下的成员:
cpu - 受影响的cpu的号//number of the affected CPU
old - 旧的频率
new - 新的频率
如果cpufreq核心检查到频率在系统挂起期间频率改变了,那么
注册在通知链的被通告者会收到以CPUFREQ_RESUMECHANGE
为第二个参数的通知。
========= governors.txt
什么是CPUFreq Governor
------------------
为了提供动态变频cf核心必须能够告诉相应的驱动程序一个“目标频率”。
那么下层的驱动程序要提供一个"->target"调用。
怎样选择在CPUfreq 策略约束范围内的哪个频率?
这要通过CF Governor。
流程如下图
图2:CF_Governor_workflow.png
Linux内核中的Governors
---------------------------------
》Performance
》Powersave
》Userspace
》Ondemand
Governor "ondemand" 依据当前的系统使用情况设置
CPU的频率。CPU必须有快速切换频率的能力。
有一组sysfs文件用于相应参数的存取。
sampling_rate:
该值以uS为单位。它指定了CPU查看系统使用情况并相应地
切换CPU频率的间隔。典型直为10000 .该值为
transition_latency * 1000
注意transiton_latency单位为nS, sampling_rate以 uS为单位,
所以通常它们的值是相同的。
up_threshold:
该值定义了CPU提高频率值的依据。比如默认的95,指示了当系统的
使用率超过95%时,那么CPU将增加频率直。
ignore_nice_load:
该参数值可以为0或者1.
0:所有的进程都将计入计算CPU使用率统计范围。
1:那些有“nice”值的进程在计算CPU使用率统计时将被忽略。
sampling_down_factor:
和up_threshold相对应。默认值为1.
》Conservative
“conservative"和”ondemand"类似,依据CPU使用率调整系统的
频率。不同之处是它的频率调节更加“光滑”,即跳变的“台阶” “小”
freq_step:
它定义了跳变的“步长“,默认为最大CPU频率的5%.
down_threshold:
类似于"ondemand"中的 “up_threshold" 但是确实朝着相反的
方向变化。默认值为20,意味着当CPU的利用率低于20%时才开始
执行频率的递减动作。
CPUFreq 核心的Governor接口
---------------------------------------
一个新的Governor必须通过"cpufreq_register_governor"向
CPUfreq核心注册。cpufreq_governor结构将会传递给CF核心。
该结构必须包含如下的值:
g->name 唯一的governor名字
g->governor 该governor 的回调函数
g->owner THIS_MODULE或者NULL
g->governor回调函数以cpufreq_policy结构和一个无符号事件标志
为参数被调用。
事件标志如下,
CPUFREQ_GOV_START:
governor要开始对 policy->cpu服务
CPUFREQ_GOV_STOP:
governor不再为policy->cpu服务
CPUFREQ_GOV_LIMITS:
告知对policy->cpu的频率界限更新为
policy->min 和 policy->max
如果你需要为你的驱动扩展“事件“的标志,只能使用
cpufreq_governor_l(unsigned int cpu, unsigned int event)
来确保对CF核心的正确加锁。
CF governor可以使用如下的两个函数来调用CPU处理器驱动:
int cpufreq_driver_target( struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation);
int __cpufreq_driver_target(struct cpu_policy *policy,
unsigned int target_freq,
unsigned int relation);
target_freq必须在policy->min和policy->max之间。
当你的governor仍然在g->governor的执行中
时,因为已经获得了per-CPU cpufreq锁,
不能再加锁了。就掉用后者,否则调用前者。
====================Documentation/cpu-freq/cpu-drivers.txt
> 初始化
在做了一些必要的检测后,调用
cpufreq_register_driver()注册cpufreq_driver结构到
CPUFreq核。
cpufreq_driver包括的成员:
*.name 驱动的名字
*.owner 通常为THIS_MODULE
*.init 到per-CPU初始化函数的指针
*.verify 到“验证“函数
*.setpolicy或者*.target 不同之处见下面解释
可选选项:
*.exit 到per-CPU清理函数的指针
*.resume 到per-CPU恢复指针,它在中断关闭的情况下,
在通过->target或者->setpolicy使前一个挂起的频率并且/或者
前一个policy恢复时调用。
*.attr 到一个以NULL结尾的"struct freq_attr"链表的指针,
该链表用来向sysfs导出值。
Per-CPU初始化
-------------------
每当一个新的CPU向设备模型注册时或者cpufreq driver注册
自己时,per-CPU初始化函数cpufreq_driver.init将被调用。
它以struct cpufreq_policy * policy为参数。
policy->cpuinfo.min_freq
policy->cpuinfo.max_freq
本CPU支持的最小和最大频率(kHz)
policy->cpuinfo.transition_latency
CPU在两种频率之间切换需要的时间(nS)
如果不切换,设置为CPUFREQ_ETERNAL
policy->cur
当前的CPU频率
policy->min
policy->max,
policy->policy, 如果有必要
policy->governor 必须包含“默认的policy"
cpufreq_driver.verify 和或者
cpufreq_driver.setpolicy或者
cpufreq_driver.target用这些值被调用。
对于这些值,使用辅助频率表将会提供便利。
verify
--------
当用户使用一个新的policy("policy,governor,min,max"应该白被设定)时,这个策略的有效性必须被检验。为了检验这些值,
定义 一个频率辅助表和/或cpufreq_verify_within_limits
(struct cpufreq_policy * policy, unsigned int min_freq,
unsigned int max_freq)函数将会很方便。
必须确保在policy->min和policy->max间至少有一个有效值。
如果有必要,首先增加policy->max仅当此方法无效时,降低
policy->min.
target还是setpolicy?
----------------------------
有些CPU频率调节算法或者驱动仅仅允许CPU使用某些固定的
频率值点。此时,你需要使用->target调用。
有些支持变频的处理器的频率可以某个区间变化。这时,需要
使用->setpolicy调用。
target
----------
setpolicy
-------------
setpolicy调用仅仅需要一个struct cpufreq_policy * policy作
参数。需要将CPU的动态频率的最低限度设置为policy->min,
最高限度设置为policy->max。并且,如果policy->policy
是CPUFREQ_POLICY_PERFORMANCE时,做有利于提高性能的设置。当policy->policy为CPUFREQ_POLICY_POWERSAVE
时,做有利于节电的设置。
频率表辅助函数
-------------------
大部分支持变频的CPU的频率可以被设定为特定的几个值。
这样设定一个“频率表”将方便处理器驱动的编写。
“频率表”由struct cpufreq_freq_table项组成,其中有
"index"和“frequency“的对应。在表的尾部,你需要添加
一个频率值为CPUFREQ_TABLE_END的cpufreq_freq_table项。
如果需要跳过某些项,将表中对应的cpufreq_freq_table项
的频率设置为CPUFREQ_ENTRY_INVALIDE。
表中的项不是必须按递增顺序排列。
#define CPUFREQ_ENTRY_INVALID ~0
#define CPUFREQ_TABLE_END ~1
struct cpufreq_frequency_table {
unsigned int index; /* any */
unsigned int frequency; /* kHz - doesn't need to be in ascending
* order */
};
cpufreq_frequency_table_cpuinfo(
struct cpufreq_policy *policy,
struct cpufreq_freqency_table *table);
通过调用本函数,cpuinfo.min_freq和cpuinfo.max
值被检测,
policy->min, policy->cpuinfo.min_freq被设置为表中的最小频率。
policy->max, policy->cpuinfo.max_freq被设置为表中的最大频率。
在per-CPU初始化阶段很有用。
int cpufreq_frequency_table_verify(
struct cpufreq_policy *policy,
struct cpu_frequency_table *table);
该函数确保在policy->min和policy->max之间至少有一个有效
频率,并且所有其他的指标都符合。
该函数对于->verify调用是有帮助的。
int cpufreq_frequency_table_target(
struct cpufreq_policy* policy,
struct cpufreq_freqency_table *table,
unsigned int target_freq,
unsigned int relation,
unsigned int *index);
该函数是->target阶段的频率表辅助函数。
将target_freq传入该函数,index将会返回频率辅助表的某一项的下标.
cpufreq_talbe[index].freqency包含了新的频率值。
cpufreq_talbe[index].index包含了你输入的“index”值
===================================================================
实例:龙芯平台的变频支持
ref:
1. 基于龙芯的软件层动态变频研究与实现
2. arch/mips/kernel/cpufreq/loongson2_cpufreq.c
3. 龙芯2f处理器用户手册
硬件条件:
CLKSEL[4:0]控制了倍频关系
CR80[2:0] 1/8为间隔调节CPU频率
驱动编写://loongson2_cpufreq.c
clockmod_table为频率辅助表。
l2_cpufreq_init():
platform_driver_register // 平台驱动是为了在支持cpufreq的处理器上自动启动该模块,arch/mips/loongson/common/platform.c
cpufreq_register_driver //注册
register_cpu_wait //更新“wait指令“的实现
l2_cpufreq_exit():
unregister_cpu_wait
cpufreq_unregister_driver
platform_driver_unregister
register_cpu_wait:
将cpu_wait赋值为loongson2_cpu_wait。*
*
cpu_wait函数指针在arch/mips/kernel/cpu-probe.c中定义。为了在MIPS CPU上
实现 “wait”指令,指向特定于CPU的函数。"wait"会停止pipeline对电量消耗减少很多。
在龙芯2f上实现为:loongson2_cpu_wait
loongson2_cpu_wait://对wait指令的模仿
具体的通过对CPU的频率设置为0,再回复CPU的频率来完成。
----------------------
l2_cpufreq_cpu_init:
/* get max cpu frequency in khz */ 以hz为单位的CPU最大频率在cpu_clock_freq中保存
频率-索引表初始化
cpufreq_frequency_table_get_attr//初始化per-cpu频率表
设置当前默认policy频率为最大可用的频率
cpufreq_frequency_table_cpuinfo //通过调用本函数,cpuinfo.min_freq和cpuinfo.max
//值被检测,policy->min, policy->cpuinfo.min_freq被设置为表中的最小频率。
//policy->max, policy->cpuinfo.max_freq被设置为表中的最大频率。
l2_cpufreq_cpu_exit:
cpufreq_frequency_table_put_attr//将per-cpu频率表指针置为NULL
l2_cpufreq_verify:
cpufreq_frequency_table_verify//该函数确保在policy->min和policy->max之间至少有一个有效
//频率,并且所有其他的指标都符合。
//该函数对于->verify调用是有帮助的。
l2_cpufreq_target:
cpufreq_frequency_table_target//该函数是->target阶段的频率表辅助函数。
//将target_freq传入该函数,index将会返回频率辅助表的某一项的下标.
//cpufreq_talbe[index].freqency包含了新的频率值。
检查如果目标频率和当前的频率值相等,那么不改变直接返回。
准备struct cpufreq_freqs 结构*
cpufreq_notify_transition// 以CPUFREQ_PRECHANGE为状态,调用通知链cpufreq_transition_notifier_list
l2_cpufreq_set//设置CPU到目标频率并且更新虚拟clocksource和 clockevent
cpufreq_notify_transition// 以CPUFREQ_POSTCHANGE为状态,调用通知链cpufreq_transition_notifier_list
*
struct cpufreq_freqs {
unsigned int cpu; /* cpu nr */
unsigned int old; //当前的频率值
unsigned int new;//目标频率值
u8 flags; /* flags of cpufreq_driver, see below. */
};
l2_cpufreq_set:
两种版本:
1》CONFIG_R4K_TIMER_FOR_CPUFREQ为真,使用R4k时
当频率升高时,先调用sync_virtual_count更新虚拟clocksource和 clockevent
再设置CR80寄存器改变CPU的频率。
当频率降低时,先设置CR80寄存器改变CPU的频率,
再调用sysnc_virtual_count更新虚拟clocksource和 clockevent
2》
l2_cpufreq_get:获取当前频率值
和时间子系统的关系:
因为改变CPU的频率会严重影响虚拟时钟的计数正确性维护。
所以要在CPU频率改变时,更新维护虚拟时钟正确性函数的
被调用间隔。比如,如果虚拟计数时钟原来每个T秒钟做一次
溢出处理,但是CPU频率升高了,那么T就必须减小,否则
会出现溢出。导致虚拟时钟计数溢出。
sync_virtual_count
sync_virtual_count:
update_virtual_count//更新虚拟clocksource和 clockevent
和其他模块和子系统的联系:
通过cpufreq_notify_transition的调用通知了其他部分CPU
频率的改变,以使得其他模块可以做相关的调整。