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

评论:
2017-03-12 14:39
请教您,系统在降低功耗的时候可以选在降低CPU的频率也可以选在关闭某一个CPU,那么到底是选择哪一种来降低功耗,这个依据有什么,您知道吗?
2015-10-19 16:01
是这句吗:DEFINE_PER_CPU(struct cpuidle_device *, cpuidle_devices);
2015-10-15 11:10
2.手机开机的时候usage就已经达到10000多次了?
刚学idle,还有许多不甚了解地方。
2015-10-15 11:33
2. idle一般用在cpu没事可干的时候,但是整个系统都是活的,知道cpu有新的事情为止。所以100000多次也正常。
2015-10-15 14:07
1.cpu没事做的时候,idle会进入wfi(state0),那肯定是要有中断才会唤醒的呀,那哪里会产生这么多次的中断,使之进入idle那么多次。再者,这么频繁的进入wfi,对于省电的贡献大吗?
2015-10-16 09:27
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?
2015-05-03 22:04
----------------------------------------------------------------------
tick的周期设定和是否配置了NO HZ无关,一般而言,普通的嵌入式系统会设定HZ=100,对于服务器平台,缺省设定为1000.
如果用了no_hz之后,周期性tick仍然是100hz,一旦idle进入了最深的级别,local timer被停止了,这时候有一个广播的timer来负责给idle 的cpu提供timer信号?是否可以理解为即使CPU进入了最深级别的idle,广播timer仍然会以100hz的频率来唤醒CPU?
------------------------------------------------------------------------
不是。1~2个星期之内会有一份文档为您解惑,敬请期待,呵呵~~~
功能
最新评论
文章分类
随机文章
文章存档
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2018-05-24 11:36
我在抓取pmu(performance monitor unit)事件时,如果内核在idle状态,则系统会挂死
请教下在内核的其他地方,想要判断当前cpu是否处于idle状态,应该怎么做?这样,我可以在读pmu前判断一下cpu的状态