找回密码
 注册
关于网站域名变更的通知
查看: 451|回复: 1
打印 上一主题 下一主题

Linux动态频率调节系统CPUFreq之系统负载的检测

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2019-12-6 09:59 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
Linux动态频率调节系统CPUFreq之系统负载的检测
" @# V1 j8 [& v. B# j' y' u9 {

6 d& {) }4 B5 L! Y上一节我们提到,核心层启动一个governor后,会在每个使用该governor的cpu上建立一个工作队列,工作队列的执行函数是在common_dbs_data中gov_dbs_timer字段所指向的函数,理所当然,该函数由各个governor的具体代码来实现,对于ondemand governor,它的实现函数是od_dbs_timer。governor的公共层代码为我们提供了一个API:dbs_check_cpu,该API用来计算两个统计周期期间某个cpu的负载情况,我们先分析一下dbs_check_cpu:* [, O$ t7 W. w  E
4 y. a+ D& ]! A5 E3 ^9 s
  • void dbs_check_cpu(struct dbs_data *dbs_data, int cpu)
  • {
  •         struct cpu_dbs_common_info *cdbs = dbs_data->cdata->get_cpu_cdbs(cpu);
  •         ......
  •         policy = cdbs->cur_policy;
  •         /* Get Absolute Load (in terms of freq for ondemand gov) */
  •         for_each_cpu(j, policy->cpus) {
  •                 struct cpu_dbs_common_info *j_cdbs;
  •                 ......
  •                 j_cdbs = dbs_data->cdata->get_cpu_cdbs(j);
  •                 ......
  •                 cur_idle_time = get_cpu_idle_time(j, &cur_wall_time, io_busy);
  •                 wall_time = (unsigned int)
  •                         (cur_wall_time - j_cdbs->prev_cpu_wall);
  •                 j_cdbs->prev_cpu_wall = cur_wall_time;
  •                 idle_time = (unsigned int)
  •                         (cur_idle_time - j_cdbs->prev_cpu_idle);
  •                 j_cdbs->prev_cpu_idle = cur_idle_time;
  •                 ......
  •                 load = 100 * (wall_time - idle_time) / wall_time;
  •                 ......
  •                 load *= cur_freq;    /* 实际的代码不是这样,为了简化讨论,精简为实际的计算逻辑*/
  •                 if (load > max_load)
  •                         max_load = load;
  •         }
  •         dbs_data->cdata->gov_check_cpu(cpu, max_load);
  • }# L5 W7 j5 y' H8 p4 t" X( \
         
; ]! O  R" ]( A- y4 r8 G$ [! e7 ]

$ ], e& R" M- n由代码可以看出,遍历该policy下每个online的cpu,取出该cpu对应的cpu_dbs_common_info结构,该结构中的prev_cpu_idle和prev_cpu_wall保存有上一次采样周期时记录的idle时间和运行时间,负载的计算其实很简单:
. {% C8 a. F* e" @" b) b( G3 ?
  • idle_time = 本次idle时间 - 上次idle时间;
  • wall_time = 本次总运行时间 - 上次总运行时间;
  • 负载load = 100 * (wall_time - idle_time)/ wall_time;
  • 把所有cpu中,负载最大值记入max_load中,作为选择频率的依据;
    # H7 g  K7 ^5 u  u, F1 u
4 N; G$ a! l( X6 Z& m; \; X" h# v

* B+ I1 m8 ^# d2 N# n  ~3 z计算出最大负载max_load后,调用具体governor实现的gov_check_cpu回调函数,对于ondemand来说,该回调函数是:od_check_cpu,我们跟进去看看:

, v+ Q* `6 [" A
  • static void od_check_cpu(int cpu, unsigned int load_freq)
  • {
  •         struct od_cpu_dbs_info_s *dbs_info = &per_cpu(od_cpu_dbs_info, cpu);
  •         struct cpufreq_policy *policy = dbs_info->cdbs.cur_policy;
  •         struct dbs_data *dbs_data = policy->governor_data;
  •         struct od_dbs_tuners *od_tuners = dbs_data->tuners;
  •         dbs_info->freq_lo = 0;
  •         /* Check for frequency increase */
  •         if (load_freq > od_tuners->up_threshold * policy->cur) {
  •                 /* If switching to max speed, apply sampling_down_factor */
  •                 if (policy->cur < policy->max)
  •                         dbs_info->rate_mult =
  •                                 od_tuners->sampling_down_factor;
  •                 dbs_freq_increase(policy, policy->max);
  •                 return;
  •         }9 u8 p8 f, G' z
  5 a  i( B& {1 ~8 f1 ?1 ?3 X+ S6 I! Q

5 H2 l( F$ O, z% c1 E6 x' [6 o: d; @4 n0 S6 H

. v9 @% w, T2 s4 o9 F; y5 M当负载比预设的阀值高时(od_tuners->up_threshold,默认值是95%),立刻选择该policy最大的工作频率作为接下来的工作频率。如果负载没有达到预设的阀值,但是当前频率已经是最低频率了,则什么都不做,直接返回:) t  Y9 A- H! {- }
  •         if (policy->cur == policy->min)
  •                 return;
    + ~  Z; W, c) ]4 M+ y2 v( U

" F4 ]- A/ }; z) z% ]4 t

3 ~# j* D1 N7 l2 p
, h& o* h- l, X$ f- R

, D; l3 g5 }$ L" f/ i" Z; l* x运行到这里,cpu的频率可能已经在上面的过程中被设置为最大频率,实际上我们可能并不需要这么高的频率,所以接着判断,当负载低于另一个预设值时,这时需要计算一个合适于该负载的新频率:
9 m& V9 X0 d' q+ u6 f! `, s3 y
- |7 s' P+ V% v5 Z9 l$ c* X; K
  •         if (load_freq < od_tuners->adj_up_threshold
  •                         * policy->cur) {
  •                 unsigned int freq_next;
  •                 freq_next = load_freq / od_tuners->adj_up_threshold;
  •                 /* No longer fully busy, reset rate_mult */
  •                 dbs_info->rate_mult = 1;
  •                 if (freq_next < policy->min)
  •                         freq_next = policy->min;
  •                 if (!od_tuners->powersave_bias) {
  •                         __cpufreq_driver_target(policy, freq_next,
  •                                         CPUFREQ_RELATION_L);
  •                         return;
  •                 }
  •                 freq_next = od_ops.powersave_bias_target(policy, freq_next,
  •                                         CPUFREQ_RELATION_L);
  •                 __cpufreq_driver_target(policy, freq_next, CPUFREQ_RELATION_L);
  •         }
  • }
    " ?; n0 o9 P! t/ x+ I4 \: ]
   
8 g3 G6 f' l! _5 F1 J: Z' \

# S  \" {; m4 U7 L* ]: J, G: J) g) A' v9 }
( o. z  f. L2 g2 }5 Z
对于ondemand来说,因为传入的负载是乘上了当前频率后的归一化值,所以计算新频率时,直接用load_freq除以想要的负载即可。本来计算出来的频率直接通过__cpufreq_driver_target函数,交给cpufreq_driver调节频率即可,但是这里的处理考虑了powersave_bias的设置情况,当设置了powersave_bias时,表明我们为了进一步节省电力,我们希望在计算出来的新频率的基础上,再乘以一个powersave_bias设定的百分比,作为真正的运行频率,powersave_bias的值从0-1000,每一步代表0.1%。实际的情况比想象中稍微复杂一点,考虑到乘以一个powersave_bias后的新频率可能不在cpu所支持的频率表中,ondemand算法会在频率表中查找,分别找出最接近新频率的一个区间,由高低两个频率组成,低的频率记入od_cpu_dbs_info_s结构的freq_lo字段中,高的频率通过od_ops.powersave_bias_target回调返回。同时,od_ops.powersave_bias_target回调函数还计算出高低两个频率应该运行的时间,分别记入od_cpu_dbs_info_s结构的freq_hi_jiffies和freq_low_jiffies字段中。原则是,通过两个不同频率的运行时间的组合,使得综合结果接近我们想要的目标频率。详细的计算逻辑请参考函数:generic_powersave_bias_target。
+ p3 y0 D( p% l' \! U

9 `3 l% j* p7 t2 p9 g% U讨论完上面两个函数,让我们回到本节的开头,负载的计算工作是在一个工作队列中发起的,前面说过,ondemand对应的工作队列的工作函数是od_dbs_timer,我们看看他的实现代码:
6 i0 I& F+ r4 Y% c4 P8 N, S

; d3 y9 y1 D- t5 H9 X( g
  • static void od_dbs_timer(struct work_struct *work)
  • {
  •         ......
  •         /* Common NORMAL_SAMPLE setup */
  •         core_dbs_info->sample_type = OD_NORMAL_SAMPLE;
  •         if (sample_type == OD_SUB_SAMPLE) {
  •                 delay = core_dbs_info->freq_lo_jiffies;
  •                 __cpufreq_driver_target(core_dbs_info->cdbs.cur_policy,
  •                                 core_dbs_info->freq_lo, CPUFREQ_RELATION_H);
  •         } else {
  •                 dbs_check_cpu(dbs_data, cpu);
  •                 if (core_dbs_info->freq_lo) {
  •                         /* Setup timer for SUB_SAMPLE */
  •                         core_dbs_info->sample_type = OD_SUB_SAMPLE;
  •                         delay = core_dbs_info->freq_hi_jiffies;
  •                 }
  •         }
  • max_delay:
  •         if (!delay)
  •                 delay = delay_for_sampling_rate(od_tuners->sampling_rate
  •                                 * core_dbs_info->rate_mult);
  •         gov_queue_work(dbs_data, dbs_info->cdbs.cur_policy, delay, modify_all);
  •         mutex_unlock(&core_dbs_info->cdbs.timer_mutex);
  • }8 n0 {. m; y' S- ^. {8 v
   ) {4 g8 ^1 u- l- J2 v/ _
; J% t' e6 R7 Y" [
6 O) T/ h% m  f: E% a$ ~% P

) w0 u! W* P$ I5 v1 C& S3 O; K9 v, x如果sample_type是OD_SUB_SAMPLE时,表明上一次采样时,需要用高低两个频率来模拟实际的目标频率中的第二步:需要运行freq_lo,并且持续时间为freq_lo_jiffies。否则,调用公共层计算负载的API:dbs_check_cpu,开始一次新的采样,当powersave_bias没有设置时,该函数返回前,所需要的新的目标频率会被设置,考虑到powersave_bias的设置情况,判断一下如果freq_lo被设置,说明需要用高低两个频率来模拟实际的目标频率,高频率已经在dbs_check_cpu返回前被设置(实际的设置工作是在od_check_cpu中),所以把sample_type设置为OD_SUB_SAMPLE,以便下一次运行工作函数进行采样时可以设置低频率运行。最后,调度工作队列在下一个采样时刻再次运行,这样,cpu的工作频率实现了在每个采样周期,根据实际的负载情况,动态地设定合适的工作频率进行运行,既满足了性能的需求,也降低了系统的功耗,达到了cpufreq系统的最终目的,整个流程可以参考下图:! A5 T3 H' `4 N3 q0 ?; d4 J" p

2 l' V/ ~* ]9 c4 Z图 1  负载计算和频率选择4 I& s: N" E# A& Z- @

' u+ t$ v) n  W2 D( B; X* z/ a
$ h& A" e( q+ O5 ]; W
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-8-24 20:50 , Processed in 0.140625 second(s), 26 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表