admin管理员组文章数量:1658598
在上一篇文章中,介绍了cpufreq的core层,core提供了cpufreq系统的初始化,公共数据结构的建立以及对cpufreq中其它子部件提供注册功能。core的最核心功能是对policy的管理,一个policy通过cpufreq_policy结构中的governor字段,和某个governor相关联,本章的内容正是要对governor进行讨论。
通过前面两篇文章的介绍,我们知道,governor的作用是:检测系统的负载状况,然后根据当前的负载,选择出某个可供使用的工作频率,然后把该工作频率传递给cpufreq_driver,完成频率的动态调节。内核默认提供了5种governor供我们使用,在之前的内核版本中,每种governor几乎是独立的代码,它们各自用自己的方式实现对系统的负载进行监测,很多时候,检测的逻辑其实是很相似的,各个governor最大的不同之处其实是根据检测的结果,选择合适频率的策略。所以,为了减少代码的重复,在我现在分析的内核版本中(3.10.0),一些公共的逻辑代码被单独抽象出来,单独用一个文件来实现:/drivers/cpufreq/cpufreq_governor.c,而各个具体的governor则分别有自己的代码文件,如:cpufreq_ondemand.c,cpufreq_performance.c。下面我们先从公共部分讨论。
1. 数据结构
cpu_dbs_common_info 该结构把对计算cpu负载需要使用到的一些辅助变量整合在了一起,通常,每个cpu都需要一个cpu_dbs_common_info结构体,该结构体中的成员会在governor的生命周期期间进行传递,以用于统计当前cpu的负载,它的定义如下:
[cpp] view plain copy print ?- /* Per cpu structures */
- struct cpu_dbs_common_info {
- int cpu;
- u64 prev_cpu_idle;
- u64 prev_cpu_wall;
- u64 prev_cpu_nice;
- struct cpufreq_policy *cur_policy;
- struct delayed_work work;
- struct mutex timer_mutex;
- ktime_t time_stamp;
- };
- cpu 与该结构体相关联的cpu编号。
- prev_cpu_idle 上一次统计时刻该cpu停留在idle状态的总时间。
- prev_cpu_wall 上一次统计时刻对应的总工作时间。
- cur_policy 指向该cpu所使用的cpufreq_policy结构。
- work 工作队列,该工作队列会被定期地触发,然后定期地进行负载的更新和统计工作。
[cpp] view plain copy print ?
- struct od_cpu_dbs_info_s {
- struct cpu_dbs_common_info cdbs;
- struct cpufreq_frequency_table *freq_table;
- unsigned int freq_lo;
- unsigned int freq_lo_jiffies;
- unsigned int freq_hi_jiffies;
- unsigned int rate_mult;
- unsigned int sample_type:1;
- };
[cpp] view plain copy print ?
- struct cs_cpu_dbs_info_s {
- struct cpu_dbs_common_info cdbs;
- unsigned int down_skip;
- unsigned int requested_freq;
- unsigned int enable:1;
- };
common_dbs_data 各个独立的governor,需要和governor的公共层交互,需要实现一套公共的接口,这个接口由common_dbs_data结构来提供:
- struct common_dbs_data {
- /* Common across governors */
- #define GOV_ONDEMAND 0
- #define GOV_CONSERVATIVE 1
- int governor;
- struct attribute_group *attr_group_gov_sys; /* one governor - system */
- struct attribute_group *attr_group_gov_pol; /* one governor - policy */
- /* Common data for platforms that don't set have_governor_per_policy */
- struct dbs_data *gdbs_data;
- struct cpu_dbs_common_info *(*get_cpu_cdbs)(int cpu);
- void *(*get_cpu_dbs_info_s)(int cpu);
- void (*gov_dbs_timer)(struct work_struct *work);
- void (*gov_check_cpu)(int cpu, unsigned int load);
- int (*init)(struct dbs_data *dbs_data);
- void (*exit)(struct dbs_data *dbs_data);
- /* Governor specific ops, see below */
- void *gov_ops;
- };
- governor 因为ondemand和conservative的实现部分有很多相似的地方,用该字段做一区分,可以设置为GOV_ONDEMAND或GOV_CONSERVATIVE的其中之一。
- attr_group_gov_sys 该公共的sysfs属性组。
- attr_group_gov_pol 各policy使用的属性组,有时候多个policy会使用同一个governor算法。
- gdbs_data 通常,当没有设置have_governor_per_policy时,表示所有的policy使用了同一种governor,该字段指向该governor的dbs_data结构。
- get_cpu_cdbs 回调函数,公共层用它取得对应cpu的cpu_dbs_common_info结构指针。
- get_cpu_dbs_info_s 回调函数,公共层用它取得对应cpu的cpu_dbs_common_info_s的派生结构指针,例如:od_cpu_dbs_info_s,cs_cpu_dbs_info_s。
- gov_dbs_timer 前面说过,cpu_dbs_common_info_s结构中有一个工作队列,该回调通常用作工作队列的工作函数。
- gov_check_cpu 计算cpu负载的回调函数,通常会直接调用公共层提供的dbs_check_cpu函数完成实际的计算工作。
- init 初始化回调,用于完成该governor的一些额外的初始化工作。
- exit 回调函数,governor被移除时调用。
- gov_ops 各个governor可以用该指针定义各自特有的一些操作接口。
dbs_data 该结构体通常由governor的公共层代码在governor的初始化阶段动态创建,该结构的一个最重要的字段就是cdata:一个common_dbs_data结构指针,另外,该结构还包含一些定义governor工作方式的一些调节参数。该结构的详细定义如下:
[cpp] view plain copy print ?- struct dbs_data {
- struct common_dbs_data *cdata;
- unsigned int min_sampling_rate;
- int usage_count;
- void *tuners;
- /* dbs_mutex protects dbs_enable in governor start/stop */
- struct mutex mutex;
- };
- cdata 一个common_dbs_data结构指针,通常由具体governor的实现部分定义好,然后作为参数,通过公共层的API:cpufreq_governor_dbs,传递到公共层,cpufreq_governor_dbs函数在创建好dbs_data结构后,把该指针赋值给该字段。
- min_sampling_rate 用于记录统计cpu负载的采样周期。
- usage_count 当没有设置have_governor_per_policy时,意味着所有的policy采用同一个governor,该字段就是用来统计目前该governor被多少个policy引用。
- tuners 指向governor的调节参数结构,不同的governor可以定义自己的tuner结构,公共层代码会在governor的初始化阶段调用common_dbs_data结构的init回调函数,governor的实现可以在init回调中初始化tuners字段。
cpufreq_governor 这个结构在本系列文章的第一篇已经介绍过了,请参看Linux动态频率调节系统CPUFreq之一:概述。几个数据结构的关系如下图所示:
图 1.1 governor的数据结构关系
下面我们以ondemand这个系统已经实现的governor为例,说明一下如何实现一个governor。具体的代码请参看:/drivers/cpufreq/cpufreq_ondemand.c。
2. 定义一个governor
要实现一个governor,首先要定义一个cpufreq_governor结构,对于ondeman来说,它的定义如下:
[cpp] view plain copy print ?- struct cpufreq_governor cpufreq_gov_ondemand = {
- .name = "ondemand",
- .governor = od_cpufreq_governor_dbs,
- .max_transition_latency = TRANSITION_LATENCY_LIMIT,
- .owner = THIS_MODULE,
- };
[cpp] view plain copy print ?
- static int od_cpufreq_governor_dbs(struct cpufreq_policy *policy,
- unsigned int event)
- {
- return cpufreq_governor_dbs(policy, &od_dbs_cdata, event);
- }
[cpp] view plain copy print ?
- static struct common_dbs_data od_dbs_cdata = {
- .governor = GOV_ONDEMAND,
- .attr_group_gov_sys = &od_attr_group_gov_sys,
- .attr_group_gov_pol = &od_attr_group_gov_pol,
- .get_cpu_cdbs = get_cpu_cdbs,
- .get_cpu_dbs_info_s = get_cpu_dbs_info_s,
- .gov_dbs_timer = od_dbs_timer,
- .gov_check_cpu = od_check_cpu,
- .gov_ops = &od_ops,
- .init = od_init,
- .exit = od_exit,
- };
[cpp] view plain copy print ?
- static DEFINE_PER_CPU(struct od_cpu_dbs_info_s, od_cpu_dbs_info);
[cpp] view plain copy print ?
- #define define_get_cpu_dbs_routines(_dbs_info) \
- static struct cpu_dbs_common_info *get_cpu_cdbs(int cpu) \
- { \
- return &per_cpu(_dbs_info, cpu).cdbs; \
- } \
- \
- static void *get_cpu_dbs_info_s(int cpu) \
- { \
- return &per_cpu(_dbs_info, cpu); \
- }
[cpp] view plain copy print ?
- define_get_cpu_dbs_routines(od_cpu_dbs_info);
3. 初始化一个governor
当一个governor被policy选定后,核心层会通过__cpufreq_set_policy函数对该cpu的policy进行设定,参看 Linux动态频率调节系统CPUFreq之二:核心(core)架构与API中的第4节和图4.1。如果policy认为这是一个新的governor(和原来使用的旧的governor不相同),policy会通过__cpufreq_governor函数,并传递CPUFREQ_GOV_POLICY_INIT参数,而__cpufreq_governor函数实际上是调用cpufreq_governor结构中的governor回调函数,在第2节中我们已经知道,这个回调最后会进入governor公共API:cpufreq_governor_dbs,下面是它收到CPUFREQ_GOV_POLICY_INIT参数时,经过简化后的代码片段:
[cpp] view plain copy print ?- case CPUFREQ_GOV_POLICY_INIT:
- ......
- dbs_data = kzalloc(sizeof(*dbs_data), GFP_KERNEL);
- ......
- dbs_data->cdata = cdata;
- dbs_data->usage_count = 1;
- rc = cdata->init(dbs_data);
- ......
- rc = sysfs_create_group(get_governor_parent_kobj(policy),
- get_sysfs_attr(dbs_data));
- ......
- policy->governor_data = dbs_data;
- ......
- /* Bring kernel and HW constraints together */
- dbs_data->min_sampling_rate = max(dbs_data->min_sampling_rate,
- MIN_LATENCY_MULTIPLIER * latency);
- set_sampling_rate(dbs_data, max(dbs_data->min_sampling_rate,
- latency * LATENCY_MULTIPLIER));
- if ((cdata->governor == GOV_CONSERVATIVE) &&
- (!policy->governor->initialized)) {
- struct cs_ops *cs_ops = dbs_data->cdata->gov_ops;
- cpufreq_register_notifier(cs_ops->notifier_block,
- CPUFREQ_TRANSITION_NOTIFIER);
- }
- if (!have_governor_per_policy())
- cdata->gdbs_data = dbs_data;
- return 0;
sampling_rate;
io_is_busy;
up_threshold;
sampling_down_factor;
ignore_nice;
powersave_bias;
sampling_rate_min;
继续,把初始化好的dbs_data结构赋值给policy的governor_data字段,以方便以后的访问。最后是通过set_sampling_rate设置governor的采样周期,如果还有设置have_governor_per_policy,把dbs_data结构指针赋值给cdata结构的gdbs_data字段,至此,governor的初始化工作完成,下面是整个过程的序列图:
图 3.1 governor的初始化
4. 启动一个governor
核心层会通过__cpufreq_set_policy函数,通过CPUFREQ_GOV_POLICY_INIT参数,在公共层的API:cpufreq_governor_dbs中,完成了对governor的初始化工作,紧接着,__cpufreq_set_policy会通过CPUFREQ_GOV_START参数,和初始化governor的流程一样,最终会到达cpufreq_governor_dbs函数中,我们看看它是如何启动一个governor的:
[cpp] view plain copy print ?- case CPUFREQ_GOV_START:
- if (!policy->cur)
- return -EINVAL;
- mutex_lock(&dbs_data->mutex);
- for_each_cpu(j, policy->cpus) {
- struct cpu_dbs_common_info *j_cdbs =
- dbs_data->cdata->get_cpu_cdbs(j);
- j_cdbs->cpu = j;
- j_cdbs->cur_policy = policy;
- j_cdbs->prev_cpu_idle = get_cpu_idle_time(j,
- &j_cdbs->prev_cpu_wall, io_busy);
- if (ignore_nice)
- j_cdbs->prev_cpu_nice =
- kcpustat_cpu(j).cpustat[CPUTIME_NICE];
- mutex_init(&j_cdbs->timer_mutex);
- INIT_DEFERRABLE_WORK(&j_cdbs->work,
- dbs_data->cdata->gov_dbs_timer);
- }
- 取出该cpu相关联的cpu_dbs_common_info结构指针,之前已经讨论过,governor定义了一个per_cpu变量来定义各个cpu所对应的cpu_dbs_common_info结构,通过common_dbs_data结构的回调函数可以获取该结构的指针。
- 初始化cpu_dbs_common_info结构的cpu,cur_policy,prev_cpu_idle,prev_cpu_wall,prev_cpu_nice字段,其中,prev_cpu_idle,prev_cpu_wall这两个字段会被以后的负载计算所使用。
- 为每个cpu初始化一个工作队列,工作队列的执行函数是common_dbs_data结构中的gov_dbs_timer字段所指向的回调函数,对于ondemand来说,该函数是:od_dbs_timer。这个工作队列会被按照设定好的采样率定期地被唤醒,进行cpu负载的统计工作。
- /* Initiate timer time stamp */
- cpu_cdbs->time_stamp = ktime_get();
- gov_queue_work(dbs_data, policy,
- delay_for_sampling_rate(sampling_rate), true);
图 4.1 启动一个governor
工作队列被调度执行后,会在工作队列的执行函数中进行cpu负载的统计工作,这个我们在下一节中讨论。
5. 系统负载的检测
上一节我们提到,核心层启动一个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: [cpp] view plain copy print ?
- 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);
- }
- idle_time = 本次idle时间 - 上次idle时间;
- wall_time = 本次总运行时间 - 上次总运行时间;
- 负载load = 100 * (wall_time - idle_time)/ wall_time;
- 把所有cpu中,负载最大值记入max_load中,作为选择频率的依据;
- 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;
- }
[cpp] view plain copy print ?
- if (policy->cur == policy->min)
- return;
[cpp] view plain copy print ?
- 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);
- }
讨论完上面两个函数,让我们回到本节的开头,负载的计算工作是在一个工作队列中发起的,前面说过,ondemand对应的工作队列的工作函数是od_dbs_timer,我们看看他的实现代码:
[cpp] view plain copy print ?
- 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);
- }
图 5.1 负载计算和频率选择
版权声明:本文标题:Linux动态频率调节系统CPUFreq之三:governor 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1729813467a1213614.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论