Linux cpuidle framework(2)_cpuidle core

作者:wowo 发布于:2014-12-30 22:38 分类:电源管理子系统

1. 前言

cpuidle core是cpuidle framework的核心模块,负责抽象出cpuidle device、cpuidle driver和cpuidle governor三个实体,并提供如下功能(可参考“Linux cpuidle framework(1)_概述和软件架构”中的软件架构):

1)向底层的cpuidle driver模块提供cpudile device和cpuidle driver的注册/注销接口。

2)向cpuidle governors提供governor的注册接口。

3)提供全局的cpuidle机制的开、关、暂停、恢复等功能。

4)向用户空间程序提供governor选择的接口。

5)向kernel sched中的cpuidle entry提供cpuidle的级别选择、进入等接口,以方便调用。

本文会以这些功能为线索,逐一展开,分析cpuidle framework的实现思路和实现原理。

2.主要数据结构

cpuidle core抽象出了cpuidle device、cpuidle driver和cpuidle governor三个数据结构,cpuidle driver和cpuidle governor比较容易理解,但cpuidle device的实际意义是什么呢?我们来一一梳理一下。

2.1 cpuidle_state

蜗蜗在“Linux cpuidle framework(1)_概述和软件架构”中提到过,cpuidle framework提出的主要背景是,很多复杂的CPU,有多种不同的idle级别。这些idle级别有不同的功耗和延迟,从而可以在不同的场景下使用。Linux kernel使用struct cpuidle_state结构抽象idle级别(后面将会统称为idle state),如下:

   1: /* include/linux/cpuidle.h */
   2: struct cpuidle_state {
   3:         char            name[CPUIDLE_NAME_LEN];
   4:         char            desc[CPUIDLE_DESC_LEN];
   5:  
   6:         unsigned int    flags;
   7:         unsigned int    exit_latency; /* in US */
   8:         int             power_usage; /* in mW */
   9:         unsigned int    target_residency; /* in US */
  10:         bool            disabled; /* disabled on all CPUs */
  11:  
  12:         int (*enter)    (struct cpuidle_device *dev,
  13:                         struct cpuidle_driver *drv,
  14:                         int index);
  15:  
  16:         int (*enter_dead) (struct cpuidle_device *dev, int index);
  17: };

name、desc,该idle state的名称和简介;

exit_latency,CPU从该idle state下返回运行状态的延迟,单位为us。它决定了CPU在idle状态和run状态之间切换的效率,如果延迟过大,将会影响系统性能;

power_usage,CPU在该idle state下的功耗,单位为mW;

target_residency,期望的停留时间,单位为us。进入和退出idle state是需要消耗额外的能量的,如果在idle状态停留的时间过短,节省的功耗少于额外的消耗,则得不偿失。governor会根据该字段,结合当前的系统情况(如可以idle多久),选择idle level;

disabled,表示该idle state在所有CPU上都不可用;

flags,idle state的特性标志,当前支持如下三个:
        CPUIDLE_FLAG_TIME_VALID,表明CPU停留于该idle state下的时间是可测量的,具体的使用场景,会在介绍governor时详细说明;
        CPUIDLE_FLAG_COUPLED,表明该idle state会同时在多个CPU上起作用,软件需要特殊处理;
        CPUIDLE_FLAG_TIMER_STOP,表明在该idle state下,timer会停止;

enter,进入该state的回调函数;

enter_dead,CPU长时间不需要工作时(称作offline),可调用该回调函数。

总结说来,cpuidle state的功能有2:
一是描述该idle state的特性,主要包括exit_latency、power_usage、target_residency。这些特性是governor制定idle策略的依据;
二是提供进入该idle state的具体方法。

2.2 cpuidle_device

现实中,并没有“cpuidle device”这样一个真实的设备,因此cpuidle device是一个虚拟设备,我们可以把它类比为“cpu idle controller”,负责实现cpuidle相关的逻辑。在多核CPU中,每个CPU core,都会对应一个cpuidle device。Linux kernel使用struct cpuidle_device抽象cpuidle device,如下:

   1: struct cpuidle_device {
   2:         unsigned int            registered:1;
   3:         unsigned int            enabled:1;
   4:         unsigned int            cpu;
   5:  
   6:         int                     last_residency;
   7:         int                     state_count;
   8:         struct cpuidle_state_usage      states_usage[CPUIDLE_STATE_MAX];
   9:         struct cpuidle_state_kobj *kobjs[CPUIDLE_STATE_MAX];
  10:         struct cpuidle_driver_kobj *kobj_driver;
  11:         struct cpuidle_device_kobj *kobj_dev;
  12:         struct list_head        device_list;
  13:  
  14: #ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED
  15:         int                     safe_state_index;
  16:         cpumask_t               coupled_cpus;
  17:         struct cpuidle_coupled  *coupled;
  18: #endif
  19: };

registered,表示设备是否已经注册到kernel中;

enabled,表示设备是否已经使能;

cpu,该cpuidle device所对应的cpu number;

last_residency,该设备上一次停留在idle状态的时间,单位为us;

state_count,该设备具备的idle state(对应struct cpuidle_state结构)的个数;

states_usage,一个struct cpuidle_state_usage类型的数组,记录了该设备的每个idle state的统计信息,包括是否使能(enable)、设备进入该state的次数(usage)和设备停留在该state的总时间(time,单位为us);

kobjs、kobj_driver、kobj_dev,用于组织sysfs,具体可参考TODO

device_list,用于将该设备添加到一个全局的链表中(cpuidle_detected_devices);

safe_state_index、coupled_cpus、coupled,和coupled cpu idle有关,后面会单独介绍。

由上面的描述可知,cpuidle device和常见device不同,struct cpuidle_device中保存的信息,都是运行时动态创建的信息,不需要driver提供任何额外信息。

2.3 cpuidle_driver

cpuidle core使用struct cpuidle_driver抽象cpuidle驱动,如下:

   1: struct cpuidle_driver {
   2:         const char              *name;
   3:         struct module           *owner;
   4:         int                     refcnt;
   5:  
   6:         /* used by the cpuidle framework to setup the broadcast timer */
   7:         unsigned int            bctimer:1;
   8:         /* states array must be ordered in decreasing power consumption */
   9:         struct cpuidle_state    states[CPUIDLE_STATE_MAX];
  10:         int                     state_count;
  11:         int                     safe_state_index;
  12:  
  13:         /* the driver handles the cpus in cpumask */
  14:         struct cpumask          *cpumask;
  15: };

bctimer,一个标志,用于指示在cpuidle driver注册和注销时,是否需要设置一个broadcast timer,有关broadcast timer后面再详细介绍;

states、state_count,该driver支持的cpuidle state及其个数。由于struct cpuidle_device中包含了某个state在该设备上是否enable的信息,这里的state应该是所有cpuidle device所支持的state的公倍数。另外,上面的注释很明确,这些states,需要以功耗大小的降序排列

safe_state_index,和coupled cpu idle有关,后面会单独介绍;

cpumask,一个struct cpumask结构的bit map指针,用于说明该driver支持哪些cpu core。

struct cpuidle_driver结构比较简单,由此可知编写cpuidle driver的主要工作量,是定义所支持的cpuidle state,以及state的enter接口。

2.4 cpuidle_governor

cpuidle core使用struct cpuidle_governor结构抽象cpuidle governor,如下:

   1: struct cpuidle_governor {
   2:         char                    name[CPUIDLE_NAME_LEN];
   3:         struct list_head        governor_list;
   4:         unsigned int            rating;
   5:  
   6:         int  (*enable)          (struct cpuidle_driver *drv,
   7:                                         struct cpuidle_device *dev);
   8:         void (*disable)         (struct cpuidle_driver *drv,
   9:                                         struct cpuidle_device *dev);
  10:  
  11:         int  (*select)          (struct cpuidle_driver *drv,
  12:                                         struct cpuidle_device *dev);
  13:         void (*reflect)         (struct cpuidle_device *dev, int index);
  14:  
  15:         struct module           *owner;
  16: };

name,该governor的名称;

governor_list,用于将该governor添加到一个全局的governors列表中(cpuidle_governors),由此可见系统允许存在多个governor;

rating,governor的级别,正常情况下,kernel会选择系统中rating值最大的governor作为当前governor(除非用于主动修改,后面会介绍);

enable/disable,governor的enable/disable回调函数,一般会在enable中进行一些初始化操作,在disable中进行反操作;

select,根据当前系统的运行状况,以及各个idle state的特性,选择一个state(即决策);

reflect,通过该回调函数,可以告知governor,系统上一次所处的idle state是哪个(即系统从哪一个state回来),具体的用处,后面再分析。

3.  功能说明

3.1 cpuidle_device管理

cpuidle_device管理主要负责cpuidle device注册/注销、使能/禁止,位于drivers/cpuidle/cpuidle.c中,由下面的四个接口实现:

   1: extern int cpuidle_register_device(struct cpuidle_device *dev); 
   2: extern void 
   3: cpuidle_unregister_device(struct cpuidle_device *dev); 
   4: extern int 
   5: cpuidle_enable_device(struct cpuidle_device *dev); 
   6: extern void 
   7: cpuidle_disable_device(struct cpuidle_device *dev); 

1)cpuidle_register_device

该接口的内部动作如下:

调用__cpuidle_device_init接口,初始化struct cpuidle_device变量,主要是将dev->states_usage、dev->last_residency等状态信息清零;

调用__cpuidle_register_device接口,将struct cpuidle_device指针保存在一个全局的per cpu的指针中(cpuidle_devices),同时将它添加到一个全局的列表中(cpuidle_detected_devices);

调用cpuidle_add_sysfs接口,添加该设备有关的sysfs文件(3.6小节会详细介绍);

调用cpuidle_enable_device接口,使能该该设备(由此可知,新注册的设备默认是使能的);

调用cpuidle_install_idle_handler接口,install idle handler。所谓的idle handler,其实就是一个内部的全局变量(initialized),后面讲到cpuidle的核心功能时,会再说明。

2)cpuidle_enable_device

我们通过cpuidle_enable_device接口,来理解一下何为idle device的enable?

调用cpuidle_get_cpu_driver接口,获取该设备对应的driver,如果设备没有绑定driver,或者当前没有governor(cpuidle_curr_governor为NULL),则不能enable,返回错误;

依据driver提供的idle state的个数(drv->state_count),初始化设备的state_count变量(dev->state_count);

调用cpuidle_add_device_sysfs接口注册cpuidle device的sysfs文件,注意和前面的cpuidle_add_sysfs不同,会在3.6小节一并介绍;

如果current governor提供了enable接口(cpuidle_curr_governor->enable),调用之;

置位设备的enabled标志(dev->enabled = 1),同时将全局变量enabled_devices加1,cpuidle_install_idle_handler/cpuidle_uninstall_idle_handler就是利用该全局变量设置或者清零initialized标志的。

cpuidle_unregister_device和cpuidle_disable_device为相反动作,不再细说。

注1:有关per-CPU变量

由前面的描述可知,在多核系统中,每个CPU会对应一个cpuidle device,因此cpuidle_register_device每被执行一次,就会注册一个不同的设备。如果cpuidle需要将它们分别记录下来,就要借助per-CPU变量,以cpuidle_devices指针为例,其声明和定义分别如下:

   1: /* include/linux/cpuidle.h */
   2: DECLARE_PER_CPU(struct cpuidle_device *, cpuidle_devices);
   3:  
   4:  
   5: /* include/linux/cpuidle.h */
   6: DEFINE_PER_CPU(struct cpuidle_device *, cpuidle_devices);

使用方法如下:per_cpu(cpuidle_devices, dev->cpu) = dev。

3.2 cpuidle driver管理

cpuidle driver管理位于drivers/cpuidle/driver.c中,主要负责cpuidle driver的注册、获取等逻辑的实现。

cpuidle_register_driver用于注册一个cpuidle driver,它主要完成如下内容:

注册之前,调用者需要提供详细的states信息(drv->states\drv->state_count);

进行一些合法性检查,包括idle state的个数是否大于零、cpuidle功能是否使能等等;

调用__cpuidle_driver_init初始化driver的内部数据,包括drv->refcnt、drv->cpumask、drv->bctimer等,下面会对这几个内部数据进行较为详细的说明;

调用__cpuidle_set_driver,将该driver“注册”到系统。何为“注册”呢?下面会详细解释;

如果drv->bctimer为1,则调用on_each_cpu_mask,在每个CPU上,执行一次cpuidle_setup_broadcast_timer,设置broadcast timer;

最后,调用poll_idle_init,为那些具有POLL idle state的平台,注册默认的poll idle state。

1)cpuidle driver注册的意义

从本质上将,cpuidle driver是一个“driver”,它驱动的对象是cpuidle device,也即CPU。在多核系统(SMP)中,会有多个CPU,也就有多个cpuidle device。如果这些device的idle功能相同(最关键的是idle state的个数、参数相同),那么一个cpuidle driver就可以驱动这些device。否则,则需要多个driver才能驱动。

基于上面的事实,cpuidle core提供一个名称为“CONFIG_CPU_IDLE_MULTIPLE_DRIVERS”的配置项,用于设置是否需要多个cpuidle driver。

如果没有使能这个配置项,说明所有CPU的idle功能相同,一个cpuidle driver即可。此时cpuidle driver的注册,就是将driver保存在一个名称为cpuidle_curr_driver的全局指针中,且只能注册一次。同理,cpuidle driver的获取,就是返回这个指针的值。如下:

   1: static struct cpuidle_driver *cpuidle_curr_driver;
   2:  
   3: /**
   4:  * __cpuidle_get_cpu_driver - return the global cpuidle driver pointer.
   5:  * @cpu: ignored without the multiple driver support
   6:  *
   7:  * Return a pointer to a struct cpuidle_driver object or NULL if no driver was
   8:  * previously registered.
   9:  */
  10: static inline struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu)
  11: {
  12:         return cpuidle_curr_driver;
  13: }
  14:  
  15: /**
  16:  * __cpuidle_set_driver - assign the global cpuidle driver variable.
  17:  * @drv: pointer to a struct cpuidle_driver object
  18:  *
  19:  * Returns 0 on success, -EBUSY if the driver is already registered.
  20:  */
  21: static inline int __cpuidle_set_driver(struct cpuidle_driver *drv)
  22: {
  23:         if (cpuidle_curr_driver)
  24:                 return -EBUSY;
  25:  
  26:         cpuidle_curr_driver = drv;
  27:  
  28:         return 0;
  29: }

另外,如果使能这个配置项,说明不同CPU的idle功能不同,每个CPU都要有一个driver。此时cpuidle idle的注册,又要依赖per cpu变量,就是将当前的注册的driver,保存在对应cpu的per cpu指针中。同理,cpuidle driver的获取,就是返回per cpu指针的值。如下:

   1: static DEFINE_PER_CPU(struct cpuidle_driver *, cpuidle_drivers);
   2:  
   3: /**
   4:  * __cpuidle_get_cpu_driver - return the cpuidle driver tied to a CPU.
   5:  * @cpu: the CPU handled by the driver
   6:  *
   7:  * Returns a pointer to struct cpuidle_driver or NULL if no driver has been
   8:  * registered for @cpu.
   9:  */
  10: static struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu)
  11: {
  12:         return per_cpu(cpuidle_drivers, cpu);
  13: }
  14:  
  15: /**
  16:  * __cpuidle_set_driver - set per CPU driver variables for the given driver.
  17:  * @drv: a valid pointer to a struct cpuidle_driver
  18:  *
  19:  * For each CPU in the driver's cpumask, unset the registered driver per CPU
  20:  * to @drv.
  21:  *
  22:  * Returns 0 on success, -EBUSY if the CPUs have driver(s) already.
  23:  */
  24: static inline int __cpuidle_set_driver(struct cpuidle_driver *drv)
  25: {
  26:         int cpu;
  27:  
  28:         for_each_cpu(cpu, drv->cpumask) {
  29:  
  30:                 if (__cpuidle_get_cpu_driver(cpu)) {
  31:                         __cpuidle_unset_driver(drv);
  32:                         return -EBUSY;
  33:                 }
  34:  
  35:                 per_cpu(cpuidle_drivers, cpu) = drv;
  36:         }
  37:  
  38:         return 0;
  39: }

2)broadcast timer功能

前面2.1小节提供过cpuidle state中的“CPUIDLE_FLAG_TIMER_STOP” flag。当cpuidle driver的idle state中有state设置了这个flag时,说明对应的CPU在进入idle state时,会停掉该CPU的local timer,此时Linux kernel的clock event framework(具体可参考本站“时间子系统”相关的文章)便不能再依赖本CPU的local timer。

针对这种情况,设计者会提供一个broadcast timer,该timer独立于所有CPU运行,并可以把tick广播到每个CPU上,因而不受idle state的影响。因此,如果cpuidle state具有STOP TIMER的特性的话,需要在driver注册时,调用clock events提供的notify接口(clockevents_notify),告知clock events模块,打开broadcast timer。如下:

   1: /* cpuidle_register_driver->__cpuidle_register_driver */
   2: static int __cpuidle_register_driver(struct cpuidle_driver *drv)
   3: {
   4:         ...
   5:         __cpuidle_driver_init(drv);
   6:         ...
   7:  
   8:         if (drv->bctimer)
   9:                 on_each_cpu_mask(drv->cpumask, cpuidle_setup_broadcast_timer,
  10:                                  (void *)CLOCK_EVT_NOTIFY_BROADCAST_ON, 1);
  11:         ...
  12: }
  13:  
  14:  
  15: static void __cpuidle_driver_init(struct cpuidle_driver *drv)
  16: {
  17:         ...
  18:  
  19:         /*
  20:          * Look for the timer stop flag in the different states, so that we know
  21:          * if the broadcast timer has to be set up.  The loop is in the reverse
  22:          * order, because usually one of the deeper states have this flag set.
  23:          */
  24:         for (i = drv->state_count - 1; i >= 0 ; i--) {
  25:                 if (drv->states[i].flags & CPUIDLE_FLAG_TIMER_STOP) {
  26:                         drv->bctimer = 1;
  27:                         break;
  28:                 }
  29:         }
  30: }
  31:         

__cpuidle_driver_init接口负责检查所有的idle state,如果有state设置了CPUIDLE_FLAG_TIMER_STOP flag,则置位drv->bctimer变量,表示需要开启broadcast timer;
注2:这里有一个细节,查找idle state时,用的是倒序,结合2.3小节的描述,idle state是以功耗大小的倒序排列的,意味着功耗比较小的state,最有可能关闭timer,因而倒序可以节省查找次数(虽然不多)。这充分体现了kerne编程人员的细致程度,值得我们学习!

然后在__cpuidle_register_driver中,根据drv->bctimer的状态,调用cpuidle_setup_broadcast_timer接口,打开具体CPU上的broadcat timer。其中on_each_cpu_mask可以在指定的CPU上运行函数(cpuidle_setup_broadcast_timer),这里就不再详细介绍了。

有关clock event的描述,可以参考“Linux时间子系统之(十六):clockevent”。

3)POLL idle state

POLL idle又称作CPU relax,是一种相对标准的idle state,在诸如64位Power PC等体系结构上,会提供这种state。如定义了CONFIG_ARCH_HAS_CPU_RELAX,cpuidle core会在注册cpuidle driver时,自动将driver的state[0]注册为POLL idle state,如下:

   1: static int poll_idle(struct cpuidle_device *dev,
   2:                 struct cpuidle_driver *drv, int index)
   3: {
   4:         local_irq_enable();
   5:         if (!current_set_polling_and_test()) {
   6:                 while (!need_resched())
   7:                         cpu_relax();
   8:         }
   9:         current_clr_polling();
  10:  
  11:         return index;
  12: }
  13:  
  14: static void poll_idle_init(struct cpuidle_driver *drv)
  15: {
  16:         struct cpuidle_state *state = &drv->states[0];
  17:  
  18:         snprintf(state->name, CPUIDLE_NAME_LEN, "POLL");
  19:         snprintf(state->desc, CPUIDLE_DESC_LEN, "CPUIDLE CORE POLL IDLE");
  20:         state->exit_latency = 0;
  21:         state->target_residency = 0;
  22:         state->power_usage = -1;
  23:         state->flags = CPUIDLE_FLAG_TIME_VALID;
  24:         state->enter = poll_idle;
  25:         state->disabled = false;
  26: }
3.3 cpuidle governor管理

governor管理位于drivers/cpuidle/governor.c中,包括governor的注册、获取和切换功能,分别由下面三个接口实现:

   1: /**
   2:  * cpuidle_switch_governor - changes the governor
   3:  * @gov: the new target governor
   4:  *
   5:  * NOTE: "gov" can be NULL to specify disabled
   6:  * Must be called with cpuidle_lock acquired.
   7:  */
   8: int cpuidle_switch_governor(struct cpuidle_governor *gov)
   9: {
  10:         struct cpuidle_device *dev;
  11:  
  12:         if (gov == cpuidle_curr_governor)
  13:                 return 0;
  14:  
  15:         cpuidle_uninstall_idle_handler();
  16:  
  17:         if (cpuidle_curr_governor) {
  18:                 list_for_each_entry(dev, &cpuidle_detected_devices, device_list)
  19:                         cpuidle_disable_device(dev);
  20:                 module_put(cpuidle_curr_governor->owner);
  21:         }
  22:  
  23:         cpuidle_curr_governor = gov;
  24:  
  25:         if (gov) {
  26:                 if (!try_module_get(cpuidle_curr_governor->owner))
  27:                         return -EINVAL;
  28:                 list_for_each_entry(dev, &cpuidle_detected_devices, device_list)
  29:                         cpuidle_enable_device(dev);
  30:                 cpuidle_install_idle_handler();
  31:                 printk(KERN_INFO "cpuidle: using governor %s\n", gov->name);
  32:         }
  33:  
  34:         return 0;
  35: }
  36:  
  37: /**
  38:  * cpuidle_register_governor - registers a governor
  39:  * @gov: the governor
  40:  */
  41: int cpuidle_register_governor(struct cpuidle_governor *gov)
  42: {
  43:         int ret = -EEXIST;
  44:  
  45:         if (!gov || !gov->select)
  46:                 return -EINVAL;
  47:  
  48:         if (cpuidle_disabled())
  49:                 return -ENODEV;
  50:  
  51:         mutex_lock(&cpuidle_lock);
  52:         if (__cpuidle_find_governor(gov->name) == NULL) {
  53:                 ret = 0;
  54:                 list_add_tail(&gov->governor_list, &cpuidle_governors);
  55:                 if (!cpuidle_curr_governor ||
  56:                     cpuidle_curr_governor->rating < gov->rating)
  57:                         cpuidle_switch_governor(gov);
  58:         }
  59:         mutex_unlock(&cpuidle_lock);
  60:  
  61:         return ret;
  62: }

cpuidle_register_governor接口的逻辑非常简单,将governor保存到一个全局链表(cpuidle_governors)中,并判断新注册governor的rating是否大于当前governor(保存在cpuidle_curr_governor指针中),如果大于,则将新注册governor切换为当前governor;

cpuidle_switch_governor用于切换当前的governor,需要注意的是,切换之前,要disable所有的device(cpuidle_disable_device),并在切换之后打开。

3.4 cpuidle整体的管理功能

整体的管理功能位于drivers/cpuidle/cpuidle.c中,负责如下事项:

1)cpuidle framework的初始化,由cpuidle_init实现

   1: /**
   2:  * cpuidle_init - core initializer
   3:  */
   4: static int __init cpuidle_init(void)
   5: {
   6:         int ret;
   7:  
   8:         if (cpuidle_disabled())
   9:                 return -ENODEV;
  10:  
  11:         ret = cpuidle_add_interface(cpu_subsys.dev_root);
  12:         if (ret)
  13:                 return ret;
  14:  
  15:         latency_notifier_init(&cpuidle_latency_notifier);
  16:  
  17:         return 0;
  18: }
  19: core_initcall(cpuidle_init);

主要完成:

调用cpuidle_add_interface接口,添加CPU global sysfs attributes;

调用latency_notifier_init接口,添加一个pm qos notifier,以便当系统有新的latency需求时,通知到cpuidle framework。有关PM QOS,会在另外idea文章中介绍,这里不再详细说明。

2)系统cpuidle功能的disable、pause、resume等功能,由如下接口实现(比较简单,不再详细说明)

   1: extern void disable_cpuidle(void); 
   2: extern void cpuidle_pause_and_lock(void); 
   3: extern void cpuidle_resume_and_unlock(void); 
   4: extern void cpuidle_pause(void); 
   5: extern void cpuidle_resume(void); 

 

3)idle state的select和enter

由下面两个接口实现:

   1: extern int cpuidle_select(struct cpuidle_driver *drv, struct cpuidle_device *dev); 
   2: extern int cpuidle_enter(struct cpuidle_driver *drv, struct cpuidle_device *dev, int index); 

cpuidle_select的实现非常简单,首先判断一个“use_deepest_state”变量,如果为1,选一个最低功耗的state即可。如果为0,调用当前governor的select函数,让governor选择。use_deepest_state的状态可以通过cpuidle_use_deepest_state接口设置;

cpuidle_enter接口根据state index,进入指定的state,调用state的enter函数,并记录相关的统计信息即可。


4)cpuidle driver注册的简单接口

由cpuidle_register接口实现,主要目的是在简单的平台上(所有cpuidle device的功能一样),省去cpuidle device的注册过程(由cpuidle core帮忙实现)。driver只需要定义各个idle state,并通过cpuidle_register注册cpuidle driver即可。arm64平台就是使用这个方式。

3.5 coupled idle

coupled idle功能是一个比较有意思的功能,设计者在该功能的源代码中(drivers\cpuidle\coupled.c)写了将近70行的注释,说明这个功能的缘由目的。因此在这里不能三言两语说明白,蜗蜗会单独开一篇文章,介绍该功能。

3.6 sysfs

cpuidle core通过sysfs,向用户控件提供了状态统计、governor选择等信息。先看一个例子,了解一下cpuidle有关的sysfs目录结构:

/sys/devices/system/cpu/cpuidle
|—current_driver
|—current_governor_ro(or available_governors & current_governor)
|—cpu0
|    |—cpuidle
|    |    |—state0
|    |    |    |—name
|    |    |    |—desc
|    |    |    |—disable
|    |    |    |—latency
|    |    |    |—power
|    |    |    |—time
|    |    |    |—usage
|    |    |—state1

|—cpu1
|    |—cpuidle

1)”/sys/devices/system/cpu/cpuidle"为cpuidle sysfs的顶级目录,由cpuidle_init注册,我们来看一下注册过程,顺便复习一下设备模型的知识。

   1: static int __init cpuidle_init(void)
   2: {
   3:         ...
   4:         ret = cpuidle_add_interface(cpu_subsys.dev_root);
   5:         ...
   6: }

上面代码以“cpu_subsys.dev_root”为参数,调用cpuidle_add_interface接口,注册sysfs interface;

“cpu_subsys”在drivers/base/cpu.c中定义,实际上是一个struct bus_type类型的变量,由cpu_dev_init调用subsys_system_register注册,结合“Linux设备模型(6)_Bus”中有关subsystem的描述,它会创建/sys/devices/system/cpu目录;

cpuidle_add_interface位于drivers/cpuidle/sysfs.c中,会调用sysfs_create_group,创建cpuidle的子目录以及default attribute,如下。

   1: static DEVICE_ATTR(current_driver, 0444, show_current_driver, NULL);
   2: static DEVICE_ATTR(current_governor_ro, 0444, show_current_governor, NULL);
   3:  
   4: static struct attribute *cpuidle_default_attrs[] = {
   5:         &dev_attr_current_driver.attr,
   6:         &dev_attr_current_governor_ro.attr,
   7:         NULL
   8: };
   9:  
  10: static DEVICE_ATTR(available_governors, 0444, show_available_governors, NULL);
  11: static DEVICE_ATTR(current_governor, 0644, show_current_governor,
  12:                    store_current_governor);
  13:  
  14: static struct attribute *cpuidle_switch_attrs[] = {
  15:         &dev_attr_available_governors.attr,
  16:         &dev_attr_current_driver.attr,
  17:         &dev_attr_current_governor.attr,
  18:         NULL
  19: };
  20:  
  21: int cpuidle_add_interface(struct device *dev)
  22: {
  23:         if (sysfs_switch)
  24:                 cpuidle_attr_group.attrs = cpuidle_switch_attrs;
  25:  
  26:         return sysfs_create_group(&dev->kobj, &cpuidle_attr_group);
  27: }

默认情况下,sysfs_create_group会使用cpuidle_default_attrs,该attribute只有两个文件:current_driver和current_governor_ro。如果使能了sysfs_switch(由bootloader传入),则允许通过sysfs改变当前的governor,使用cpuidle_switch_attrs,提供current_driver、available_governors和 current_governor三个attribute文件。

2)current_driver、current_governor_ro/available_governors、current_governor

通过current_driver attribute文件,可以获取当前的cpuidle driver名字;

如果没有使能sysfs switch,通过current_governor_ro,可以获取当前的governor名字;

如果使能sysfs switch,通过available_governors,可以获取可供使用的governors;通过current_governor,可以读取或者设置当前的governor。

上面attribute文件的注册,已经在上面1)中说明。

3)/sys/devices/system/cpu/cpuidle/cpuX/cpuidle/stateX

这个目录下的attribute文件提供了指定cpuidle device下指定state的统计信息,包括:

name,名称;

desc,较为详细的描述;

disable,cpuidle使能状态的读取和设置;

latency,退出该state所需的时间,单位为us;

power,该state下的功耗,单位为mW;

time,停留在改状态的总时间,单位为us

usage,进入该状态的次数。

这些attribute文件的注册过程,这里就不再描述,感兴趣的同学可以参考drivers/cpuidle/sysfs.c的实现。下面贴一个统计信息的实例,供大家参考:

[xxx@cs state0]# cat name; cat desc; cat latency; cat power; cat time; cat usage

POLL

CPUIDLE CORE POLL IDLE

0

4294967295

6777250341

563407

原创文章,转发请注明出处。蜗窝科技www.wowotech.net

标签: Linux framework core cpuidle

评论:

cazima
2018-05-24 11:36
hi,wowo
我在抓取pmu(performance monitor unit)事件时,如果内核在idle状态,则系统会挂死
请教下在内核的其他地方,想要判断当前cpu是否处于idle状态,应该怎么做?这样,我可以在读pmu前判断一下cpu的状态
陈茹
2019-10-30 09:51
@cazima:您好,请问您的问题得到解决了吗,我现在也遇到同样问题,想向您请教
paoshapaoxue
2017-03-12 14:39
wowo,
   请教您,系统在降低功耗的时候可以选在降低CPU的频率也可以选在关闭某一个CPU,那么到底是选择哪一种来降低功耗,这个依据有什么,您知道吗?
wowo
2017-03-13 15:27
@paoshapaoxue:这应该根据实际的硬件情况、应用场景去综合判断,可能还需要一些实验数据。
zyq1228
2016-05-11 19:19
@wowo,有一个困扰我的问题想请问一下:cpu idle和cpu suspend的关系是什么呀?
wowo
2016-05-12 09:03
@zyq1228:idle和suspend的低功耗级别不同。
从硬件角度看,suspend会关掉更多的clock和power,不过具体要看CPU平台的具体实现。
从软件角度看,idle对系统的大部分软件透明,也就要求系统可以随时回来。suspend则不会,很多时候需要软件参与才能进入,并且不是所有事件都能唤醒。
cym
2015-10-19 16:01
cpuidle_device的定义(实例化)在哪里呢?
是这句吗:DEFINE_PER_CPU(struct cpuidle_device *, cpuidle_devices);
wowo
2015-10-20 08:56
@cym:有两个:
DEFINE_PER_CPU(struct cpuidle_device *, cpuidle_devices);                      
DEFINE_PER_CPU(struct cpuidle_device, cpuidle_dev);    
后面是真正的变量,前面是指针,方便后面使用。
cym
2015-10-15 09:59
hi wowo
    请问下,我们知道中断会使cpu退出wfi,而这些中断是很经常发生的(进入/退出wfi很频繁),那这些中断主要都是些什么类型的中断呢?
wowo
2015-10-15 10:27
@cym:正常情况下,几乎任何类型的中断,都应该退出wfi。如果系统繁忙,idle对省电的贡献是不大的。
cym
2015-10-15 11:10
@wowo:1.当手机待机的时候,是什么中断导致进入idle 的standby的呢?
2.手机开机的时候usage就已经达到10000多次了?
刚学idle,还有许多不甚了解地方。
wowo
2015-10-15 11:33
@cym:1. 手机待机通常不会进idle,而是进standby等低功耗状态。
2. idle一般用在cpu没事可干的时候,但是整个系统都是活的,知道cpu有新的事情为止。所以100000多次也正常。
cym
2015-10-15 14:07
@wowo:hi wowo
1.cpu没事做的时候,idle会进入wfi(state0),那肯定是要有中断才会唤醒的呀,那哪里会产生这么多次的中断,使之进入idle那么多次。再者,这么频繁的进入wfi,对于省电的贡献大吗?
wowo
2015-10-15 15:04
@cym:比如系统tick,比如数据传输的中断,等等。
如果系统繁忙,频繁进出idle,省电性能会打折扣的。但CPU那么快,还是能省一些的。
cym
2015-10-15 15:41
@wowo:系统的tick是指时钟中断吗?
cym
2015-10-15 16:36
@wowo:tick中断不是周期性发生的吗,那岂不是每次进入wfi就会被tick打断了?
linuxer
2015-10-16 08:14
@wowo:tick中断不是周期性发生的吗,那岂不是每次进入wfi就会被tick打断了?
------------------------------------------------------------------
是的,如果配置成周期性tick的话,每次周期性tick到来的时候就会打断idle
cym
2015-10-16 08:55
@linuxer:在cpu_idle_loop里,有执行了tick_nohz_idle_enter,是不是表示已经关闭时钟了呢?
linuxer
2015-10-16 09:27
@cym:看你的系统配置,如果配置了dynamic tick,那么tick_nohz_idle_enter不是空函数,在该函数中会根据当前系统设定的timer的情况设定下次tick的时间点。
cym
2015-10-16 10:23
@cym:好的,谢谢大虾!
quote
2015-05-03 14:09
请教:
  如果系统用了no_hz,有一个周期性的tick存在,这个tick的周期一般是多少?之前linux一般设置为100hz,在用了no_hz之后,还是100hz吗?
   如果用了no_hz之后,周期性tick仍然是100hz,一旦idle进入了最深的级别,local timer被停止了,这时候有一个广播的timer来负责给idle 的cpu提供timer信号?是否可以理解为即使CPU进入了最深级别的idle,广播timer仍然会以100hz的频率来唤醒CPU?
linuxer
2015-05-03 22:04
@quote:如果系统用了no_hz,有一个周期性的tick存在,这个tick的周期一般是多少?
----------------------------------------------------------------------
tick的周期设定和是否配置了NO HZ无关,一般而言,普通的嵌入式系统会设定HZ=100,对于服务器平台,缺省设定为1000.


如果用了no_hz之后,周期性tick仍然是100hz,一旦idle进入了最深的级别,local timer被停止了,这时候有一个广播的timer来负责给idle 的cpu提供timer信号?是否可以理解为即使CPU进入了最深级别的idle,广播timer仍然会以100hz的频率来唤醒CPU?
------------------------------------------------------------------------
不是。1~2个星期之内会有一份文档为您解惑,敬请期待,呵呵~~~
Tekkaman Ninja
2015-04-24 18:03
并判断新注册governor的rating是狗大于当前governor
well, Pinyin input method   :-)
wowo
2015-04-27 21:46
@Tekkaman Ninja:已改正,多谢提醒。Haha, Pinyin input method :-)
schedule
2015-02-03 14:11
cpuidle 子系统70% 以上代码都在和sysfs 较劲。
wowo
2015-02-03 16:48
@schedule:也不全是这样的,governor还有不少代码呢。
schedule
2015-02-03 13:36
1.禁止cpuidle
2.将cpufreq_scaling_max 调成最大

以上两点可以保证cpu 永远在最大负载运行

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog