cpufreq子系统专题

来源:互联网 发布:php 获取数据类型 编辑:程序博客网 时间:2024/04/29 11:58

---------------
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
频率的改变,以使得其他模块可以做相关的调整。


原创粉丝点击