前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux电源驱动-Linux Cpuidle Framework

Linux电源驱动-Linux Cpuidle Framework

作者头像
DragonKingZhu
发布2020-03-24 17:23:08
22.2K0
发布2020-03-24 17:23:08
举报
文章被收录于专栏:Linux内核深入分析

前言

现如今,Linux处理器电源管理重点聚焦在处理器处于运行状态时对其进行电源管理,主要的技术是Cpufreq: 根据cpu的负载,实时的改变cpu的频率或这电压,同时管理处理器的性能水平和电源功耗等。相反当处理器处于空闲状态,也就是idle状态时的功耗也需要进行管理。也就是本文需要讨论的重点: Cpuidle。

目前市场中几乎所有的移动处理器都支持多处理器空闲状态的概念,而且每个状态下的消耗不同。而是否进入空闲状态,需要两个重要的参考因素。进入-退出空闲状态的延迟,以及在空闲状态下的消耗。如果进入-退出空闲状态的延迟很大,而待在空闲状态的时间又很短,则岂不是得不偿失。所以是否进入空闲状态也许要好的策略。

面对在火热移动处理器市场的前提下,众多处理器都亟待加入到空闲状态下进行功耗管理,这时候linux系统就需要一套通用的架构来管理这些cpu,这就是本文的标题: Linux Cpuidle Framework。

Linux Cpuidle Framework

cpuidle的代码位于: kernel/drivers/cpuidle下,以下是使用tree命令查看此目录下的结构

代码语言:javascript
复制
root@test:~/test/kernel/drivers/cpuidle$ tree
├── coupled.c
├── cpuidle-arm64.c
├── cpuidle-at91.c
├── cpuidle-big_little.c
├── cpuidle.c
├── cpuidle-calxeda.c
├── cpuidle-clps711x.c
├── cpuidle-cps.c
├── cpuidle-exynos.c
├── cpuidle.h
├── cpuidle-kirkwood.c
├── cpuidle-mvebu-v7.c
├── cpuidle-powernv.c
├── cpuidle-pseries.c
├── cpuidle-ux500.c
├── cpuidle-zynq.c
├── driver.c
├── dt_idle_states.c
├── dt_idle_states.h
├── governor.c
├── governors
│   ├── ladder.c
│   ├── Makefile
│   └── menu.c
├── Kconfig
├── Kconfig.arm
├── Kconfig.arm64
├── Kconfig.mips
├── Kconfig.powerpc
├── Makefile
└── sysfs.c

其中Cpuidle Framework分为三大部分,分别是cpuidle core,cpuidle driver,cpuidle governor。

cpuidle core主要是负责cpuidle的整体框架,主要代码由cpuidle.c实现。

cpuidle driver是各个不同cpu的cpuidle的具体实现。负责何时进入idle, 进入idle状态的具体操作等,一般文件都是cpuidle-xxx.c实现。

cpuidle governor主要负责cpu进入idle状态的策略,目前主要是ladder和menu策略。

接下来就围绕上述三个方面展开对cpuidle framework的分析。

数据结构

关于cpuidle的所有结构体都存放在cpuidle.h中。

在上面说过,目前多处理器都支持多种idle状态,而且在每个状态下的功耗不同。linux使用cpuidle_state结构用来表示各个idle状态。

代码语言:javascript
复制
struct cpuidle_state {
	char		name[CPUIDLE_NAME_LEN];
	char		desc[CPUIDLE_DESC_LEN];

	unsigned int	flags;
	unsigned int	exit_latency; /* in US */
	int		power_usage; /* in mW */
	unsigned int	target_residency; /* in US */
	bool		disabled; /* disabled on all CPUs */

	int (*enter)	(struct cpuidle_device *dev,
			struct cpuidle_driver *drv,
			int index);

	int (*enter_dead) (struct cpuidle_device *dev, int index);
};

.name: 该idle状态的名字,用于容易识别。

.desc: 该idle状态的详细描述。

.flag: idle state的特殊标识,有以下几种。

CPUIDLE_FLAG_TIME_VALID: 表示该idle状态下,时间是可用的。 CPUIDLE_FLAG_COUPLED : 表示该idle状态会应用到多个cpu上。 CPUIDLE_FLAG_TIMER_STOP: 表示该idle状态下timer会停止的。

.exit_latency: 表示cpu从idle状态下退出时的延迟,单位us。它决定了cpu从此状态下返回的效率,如果延迟过大,必将影响效率。

.power_usage: 表示cpu在此idle下的功耗,单位为mW。

.target_residency: 表示该cpu在idle下的期待时间,单位us。

.disable: 表示在所有cpu上不可用该idle statue。

.enter: 进入该idle state的回调函数。

.enter_dead: 在cpu长时间不工作,可以调用该回调。

比如ARM64下的idle state0的表示如下:

代码语言:javascript
复制
	.states[0] = {
		.enter                  = arm64_enter_idle_state,
		.exit_latency           = 1,
		.target_residency       = 1,
		.power_usage		    = UINT_MAX,
		.flags                  = CPUIDLE_FLAG_TIME_VALID,
		.name                   = "WFI",
		.desc                   = "ARM64 WFI",
	}

WFI(Wait For Interrupt)通常会作为ARM架构下idle等级0的实现,在此模式下会关闭core的clock,用来达到节省资源。

对该cpu下的idle状态,需要一个统一的管理者,这时候cpuidle驱动就来管理该cpu下的各种状态。linux使用cpuidle_driver结构来表示cpuidle的驱动。

代码语言:javascript
复制
struct cpuidle_driver {
	const char		*name;
	struct module 		*owner;
	int                     refcnt;

        /* used by the cpuidle framework to setup the broadcast timer */
	unsigned int            bctimer:1;
	/* states array must be ordered in decreasing power consumption */
	struct cpuidle_state	states[CPUIDLE_STATE_MAX];
	int			state_count;
	int			safe_state_index;

	/* the driver handles the cpus in cpumask */
	struct cpumask		*cpumask;
};

.name: 驱动名字。

.refcnt: 该驱动的引用计数。

.bctimer: 用于在cpuidle driver的注册时候是否创建broadcase timer。

.cpuidle_state: 该cpu下共支持的idle state,所有的state按照功耗大小降序排序。最大可支持10种idle状态。

.state_count: 支持的idle state的个数。

.cpumask: cpumask指针,用于说明该驱动都之后的cpu core。 cpuidle_driver用于管理该平台下的所有cpu的idle state状态,如果该平台下有8个cpu,则有可能每个cpu的idle状态一样,或者都不一样,或者某个cpu就不想有idle,一直想干活。linux系统使用cpuidle_device结构抽象每个cpu core,可以简单认为cpuidle_device对应到每个cpu core上。

代码语言:javascript
复制
struct cpuidle_device {
	unsigned int		registered:1;
	unsigned int		enabled:1;
	unsigned int		cpu;

	int			last_residency;
	int			state_count;
	struct cpuidle_state_usage	states_usage[CPUIDLE_STATE_MAX];
	struct cpuidle_state_kobj *kobjs[CPUIDLE_STATE_MAX];
	struct cpuidle_driver_kobj *kobj_driver;
	struct cpuidle_device_kobj *kobj_dev;
	struct list_head 	device_list;

#ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED
	int			safe_state_index;
	cpumask_t		coupled_cpus;
	struct cpuidle_coupled	*coupled;
#endif
};

.registered: 代表设备是否已经注册到系统了。

.enable: 代表设备是否已经使能。

.cpu: 该cpuidle_device对应的cpu number。

.last_residency: 上次设备停留在idle状态的时间。

.state_count: 表示该cpu共支持idle state的个数。

.states_usage: 表示cpu在各个idle state的使用情况,比如time, 是否使能等。

代码语言:javascript
复制
struct cpuidle_state_usage {
	unsigned long long	disable;
	unsigned long long	usage;
	unsigned long long	time; /* in US */
};

.kobjs/kobj_driver/kobj_dev: 用于在sys中构建cpuidle。

.device_list: 用于将设备使用链表管理起来。

关于该cpu何时进入idle状态,进入停留idle状态多长时间,这都由governor策略来控制,linux内核使用cpuidle_governor结构来表示governor对象。

代码语言:javascript
复制
struct cpuidle_governor {
	char			name[CPUIDLE_NAME_LEN];
	struct list_head 	governor_list;
	unsigned int		rating;

	int  (*enable)		(struct cpuidle_driver *drv,
					struct cpuidle_device *dev);
	void (*disable)		(struct cpuidle_driver *drv,
					struct cpuidle_device *dev);

	int  (*select)		(struct cpuidle_driver *drv,
					struct cpuidle_device *dev);
	void (*reflect)		(struct cpuidle_device *dev, int index);

	struct module 		*owner;
};

.name: governor的名字。

.governor_list: 使用链表管理系统中的governor。

.rating: governor的级别,张常情况下,系统会选择系统中raing值最大的governor作为当前的governor。

.enable/disbale: enbable/disable governor的回调函数。

.select: 根据系统当前的情况,选择合适的idle state。

.reflect: 通过调用该回调,可以告知该governor,上一次是处于那个idle state状态。

代码分析

cpuidle驱动注册,通过cpuidle_register_driver函数。

代码语言:javascript
复制
int cpuidle_register_driver(struct cpuidle_driver *drv)
{
	int ret;

	spin_lock(&cpuidle_driver_lock);
	ret = __cpuidle_register_driver(drv);
	spin_unlock(&cpuidle_driver_lock);

	return ret;
}

此参数cpuidle_driver是具体的cpu传入进来的。

代码语言:javascript
复制
static int __cpuidle_register_driver(struct cpuidle_driver *drv)
{
	int ret;

	if (!drv || !drv->state_count)
		return -EINVAL;

	if (cpuidle_disabled())
		return -ENODEV;

	__cpuidle_driver_init(drv);

	ret = __cpuidle_set_driver(drv);
	if (ret)
		return ret;

	if (drv->bctimer)
		on_each_cpu_mask(drv->cpumask, cpuidle_setup_broadcast_timer,
				 (void *)CLOCK_EVT_NOTIFY_BROADCAST_ON, 1);

	poll_idle_init(drv);

	return 0;
}

1. 判断当前参数驱动和state_count是否存在,cpuidle是否使能。

2. driver内部参数的初始化。

代码语言:javascript
复制
static void __cpuidle_driver_init(struct cpuidle_driver *drv)
{
	int i;

	drv->refcnt = 0;

	/*
	 * Use all possible CPUs as the default, because if the kernel boots
	 * with some CPUs offline and then we online one of them, the CPU
	 * notifier has to know which driver to assign.
	 */
	if (!drv->cpumask)
		drv->cpumask = (struct cpumask *)cpu_possible_mask;

	/*
	 * Look for the timer stop flag in the different states, so that we know
	 * if the broadcast timer has to be set up.  The loop is in the reverse
	 * order, because usually one of the deeper states have this flag set.
	 */
	for (i = drv->state_count - 1; i >= 0 ; i--) {
		if (drv->states[i].flags & CPUIDLE_FLAG_TIMER_STOP) {
			drv->bctimer = 1;
			break;
		}
	}
}

cpuilde驱动的索引数初始化,设置drv的cpumask参数,通过当前驱动支持的state个数,逐一扫描判断当前idle状态是否设置了TIMER_STOP标志,如果设置此标志,则将bctimer设置为1。

3. 设置该driver支持的cpu,这里会通过此配置CONFIG_CPU_IDLE_MULTIPLE_DRIVERS来区分,如果系统没有配置此选项,则系统中每个cpu对应的idle驱动是相同的,会设置driver到全局变量cpuidle_curr_driver,获取的时候也通过该变量获取当前的idle驱动即可。

代码语言:javascript
复制
static inline struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu)
{
	return cpuidle_curr_driver;
}

static inline int __cpuidle_set_driver(struct cpuidle_driver *drv)
{
	if (cpuidle_curr_driver)
		return -EBUSY;

	cpuidle_curr_driver = drv;

	return 0;
}

如果设置该配置,则每个cpu所对应的idle驱动就有所不同了,这里会将每个cpu对应的驱动设置到per_cpu变量cpuidle_drivers中保存的,获取的时候从该per_cpu变量中获取。

代码语言:javascript
复制
static inline int __cpuidle_set_driver(struct cpuidle_driver *drv)
{
	int cpu;

	for_each_cpu(cpu, drv->cpumask) {

		if (__cpuidle_get_cpu_driver(cpu)) {
			__cpuidle_unset_driver(drv);
			return -EBUSY;
		}

		per_cpu(cpuidle_drivers, cpu) = drv;
	}

	return 0;
}

static struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu)
{
	return per_cpu(cpuidle_drivers, cpu);
}

4. 这里会根据bctimer是否为1,初始化一个broadcast timer的。当idle state中有设置CPUIDLE_FLAG_TIMER_STOP标志的话,会在cpu进入idle state之后,会停止该cpu的local timer。此时就需要提供一个broadcast timer,该timer独立于所有cpu,并且可以把tick广播发送到每个cpu上的。

cpuidle_setup_broadcast_timer函数会为当前的cpu设置clockevent通知链。

代码语言:javascript
复制
static void cpuidle_setup_broadcast_timer(void *arg)
{
	int cpu = smp_processor_id();
	clockevents_notify((long)(arg), &cpu);
}

cpuidle_device的注册,是通过cpuidle_register_device函数实现。

代码语言:javascript
复制
int cpuidle_register_device(struct cpuidle_device *dev)
{
	int ret = -EBUSY;

	if (!dev)
		return -EINVAL;

	mutex_lock(&cpuidle_lock);

	if (dev->registered)
		goto out_unlock;

	__cpuidle_device_init(dev);

	ret = __cpuidle_register_device(dev);
	if (ret)
		goto out_unlock;

	ret = cpuidle_add_sysfs(dev);
	if (ret)
		goto out_unregister;

	ret = cpuidle_enable_device(dev);
	if (ret)
		goto out_sysfs;

	cpuidle_install_idle_handler();

out_unlock:
	mutex_unlock(&cpuidle_lock);

	return ret;

out_sysfs:
	cpuidle_remove_sysfs(dev);
out_unregister:
	__cpuidle_unregister_device(dev);
	goto out_unlock;
}

1. 通过registered变量判断该idle device是否已经注册,如果注册过则退出。

2. 调用__cpuidle_device_init进行idle device变量的初始化。主要是将states_usage和last_residency清0。

代码语言:javascript
复制
static void __cpuidle_device_init(struct cpuidle_device *dev)
{
	memset(dev->states_usage, 0, sizeof(dev->states_usage));
	dev->last_residency = 0;
}

3. 调用__cpuidle_register_device函数进行cpuilde_device的设置。

代码语言:javascript
复制
static int __cpuidle_register_device(struct cpuidle_device *dev)
{
	int ret;
	struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);

	if (!try_module_get(drv->owner))
		return -EINVAL;

	per_cpu(cpuidle_devices, dev->cpu) = dev;
	list_add(&dev->device_list, &cpuidle_detected_devices);

	ret = cpuidle_coupled_register_device(dev);
	if (ret)
		__cpuidle_unregister_device(dev);
	else
		dev->registered = 1;

	return ret;
}

会通过cpuidle_get_cpu_driver函数获取cpuidle_device对应的驱动程序,此处的获取驱动程序也是存在两种方式,一种是从cpuidle_curr_driver中获取,另一种是从每个cpu的cpuidle_drivers链表中获取,此内容在注册驱动的时候已经涉及到。

然后会将该cpuidle_device设备添加到per_cpu链表cpuidle_devices链表中,同时将该设备cpuidle_device添加到全局链表cpuidle_detected_devices中。

代码语言:javascript
复制
LIST_HEAD(cpuidle_detected_devices);
DEFINE_PER_CPU(struct cpuidle_device *, cpuidle_devices);

4. 通过此函数cpuidle_add_sysfs,在/sys/devices/system/cpu/cpuX下创建cpuidle目录。

代码语言:javascript
复制
int cpuidle_add_sysfs(struct cpuidle_device *dev)
{
	struct cpuidle_device_kobj *kdev;
	struct device *cpu_dev = get_cpu_device((unsigned long)dev->cpu);
	int error;

	kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
	if (!kdev)
		return -ENOMEM;
	kdev->dev = dev;
	dev->kobj_dev = kdev;

	init_completion(&kdev->kobj_unregister);

	error = kobject_init_and_add(&kdev->kobj, &ktype_cpuidle, &cpu_dev->kobj,
				   "cpuidle");
	if (error) {
		kfree(kdev);
		return error;
	}

	kobject_uevent(&kdev->kobj, KOBJ_ADD);

	return 0;
}

最终会通过uevent机制在系统中创建此cpuidle对象。

5. 调用cpuidle_enable_device函数使能该设备。

代码语言:javascript
复制
int cpuidle_enable_device(struct cpuidle_device *dev)
{
	int ret;
	struct cpuidle_driver *drv;

	if (!dev)
		return -EINVAL;

	if (dev->enabled)
		return 0;

	drv = cpuidle_get_cpu_driver(dev);

	if (!drv || !cpuidle_curr_governor)
		return -EIO;

	if (!dev->registered)
		return -EINVAL;

	if (!dev->state_count)
		dev->state_count = drv->state_count;

	ret = cpuidle_add_device_sysfs(dev);
	if (ret)
		return ret;

	if (cpuidle_curr_governor->enable &&
	    (ret = cpuidle_curr_governor->enable(drv, dev)))
		goto fail_sysfs;

	smp_wmb();

	dev->enabled = 1;

	enabled_devices++;
	return 0;

fail_sysfs:
	cpuidle_remove_device_sysfs(dev);

	return ret;
}

a. 获取该设备的驱动,如果没有获取到返回错误。

b. 判断设备是否已经注册,没有注册返回错误。

c. 通过驱动的state_count设置设备的state_count。

d. 调用此函数cpuidle_add_device_sysfs设置cpuidle的state属性。这些属性会根据device的state_count的个数,创建state_count个数的state目录。最终会创建以下几个文件:

代码语言:javascript
复制
root@test:/sys/devices/system/cpu/cpu0/cpuidle/state0 # ls -l
-r--r--r-- root     root         4096 2012-01-05 01:09 desc
-rw-r--r-- root     root         4096 2012-01-05 01:09 disable
-r--r--r-- root     root         4096 2012-01-05 01:09 latency
-r--r--r-- root     root         4096 2012-01-05 01:09 name
-r--r--r-- root     root         4096 2012-01-05 01:09 power
-r--r--r-- root     root         4096 2012-01-05 01:09 residency
-r--r--r-- root     root         4096 2012-01-05 01:09 time
-r--r--r-- root     root         4096 2012-01-05 01:09 usage

以上的文件,基本上就是该state的详细参数说明,也就是cpuidle_state结构体的详细说明。

e. 判断governor是否有enabel回调,有的话就调用此回调。

f. 设备enable标志赋1, 同时enable_devices加1。

cpuidle的策略governor的注册,是通过cpuidle_register_governor函数完成。

代码语言:javascript
复制
int cpuidle_register_governor(struct cpuidle_governor *gov)
{
	int ret = -EEXIST;

	if (!gov || !gov->select)
		return -EINVAL;

	if (cpuidle_disabled())
		return -ENODEV;

	mutex_lock(&cpuidle_lock);
	if (__cpuidle_find_governor(gov->name) == NULL) {
		ret = 0;
		list_add_tail(&gov->governor_list, &cpuidle_governors);
		if (!cpuidle_curr_governor ||
		    cpuidle_curr_governor->rating < gov->rating)
			cpuidle_switch_governor(gov);
	}
	mutex_unlock(&cpuidle_lock);

	return ret;
}

可以通过此函数注册governor策略,目前内核提供了两种策略: Menu和Ladder策略。此函数会将当前governor添加到全局链表cpuidle_governors中,然后会根据当前governor的rating和注册的rating比较,选择切换。

代码语言:javascript
复制
int cpuidle_switch_governor(struct cpuidle_governor *gov)
{
	struct cpuidle_device *dev;

	if (gov == cpuidle_curr_governor)
		return 0;

	cpuidle_uninstall_idle_handler();

	if (cpuidle_curr_governor) {
		list_for_each_entry(dev, &cpuidle_detected_devices, device_list)
			cpuidle_disable_device(dev);
		module_put(cpuidle_curr_governor->owner);
	}

	cpuidle_curr_governor = gov;

	if (gov) {
		if (!try_module_get(cpuidle_curr_governor->owner))
			return -EINVAL;
		list_for_each_entry(dev, &cpuidle_detected_devices, device_list)
			cpuidle_enable_device(dev);
		cpuidle_install_idle_handler();
		printk(KERN_INFO "cpuidle: using governor %s\n", gov->name);
	}

	return 0;
}

a. 如果传入的参数gov就是当前系统使用的cpuidle_curr_governor,则直接退出。

b. 会对注册的cpuidle_device设备,依次调用cpuidle_disable_device函数,disable设备。

代码语言:javascript
复制
void cpuidle_disable_device(struct cpuidle_device *dev)
{
	struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);

	if (!dev || !dev->enabled)
		return;

	if (!drv || !cpuidle_curr_governor)
		return;

	dev->enabled = 0;

	if (cpuidle_curr_governor->disable)
		cpuidle_curr_governor->disable(drv, dev);

	cpuidle_remove_device_sysfs(dev);
	enabled_devices--;
}

disable设备就是调用governor的disable函数,同时设置标志位,在sysfs中移除相应的设备等。

c. 将要当前切换的gov设置到cpuidle_curr_governor上。

d. 依次有调用cpuidle_enable_device使能设备。

cpuidle整体分析,从cpuidle_init开始着手分析。

代码语言:javascript
复制
static int __init cpuidle_init(void)
{
	int ret;

	if (cpuidle_disabled())
		return -ENODEV;

	ret = cpuidle_add_interface(cpu_subsys.dev_root);
	if (ret)
		return ret;

	latency_notifier_init(&cpuidle_latency_notifier);

	return 0;
}

1. 创建cpuidle的设备属性,会在/sys/devices/system/cpu下创建一个名字为"cpuidle"的目录。其中存在两个属性current_driver和current_governor_ro。

代码语言:javascript
复制
static struct attribute *cpuidle_default_attrs[] = {
	&dev_attr_current_driver.attr,
	&dev_attr_current_governor_ro.attr,
	NULL
};

static DEVICE_ATTR(available_governors, 0444, show_available_governors, NULL);
static DEVICE_ATTR(current_governor, 0644, show_current_governor,
		   store_current_governor);

static struct attribute *cpuidle_switch_attrs[] = {
	&dev_attr_available_governors.attr,
	&dev_attr_current_driver.attr,
	&dev_attr_current_governor.attr,
	NULL
};

static struct attribute_group cpuidle_attr_group = {
	.attrs = cpuidle_default_attrs,
	.name = "cpuidle",
};

/**
 * cpuidle_add_interface - add CPU global sysfs attributes
 */
int cpuidle_add_interface(struct device *dev)
{
	if (sysfs_switch)
		cpuidle_attr_group.attrs = cpuidle_switch_attrs;

	return sysfs_create_group(&dev->kobj, &cpuidle_attr_group);
}

2. 调用latency_notifier_init注册一个,添加一个pm qos通知链。

代码语言:javascript
复制
static struct notifier_block cpuidle_latency_notifier = {
	.notifier_call = cpuidle_latency_notify,
};

cpuidle的注册,调用cpuidle_register函数,此函数通常会在各个cpu对应的cpuidle驱动中注册。比如cpuidle_exynos.c中描述的。

代码语言:javascript
复制
static int exynos_cpuidle_probe(struct platform_device *pdev)
{
	int ret;

	exynos_enter_aftr = (void *)(pdev->dev.platform_data);

	ret = cpuidle_register(&exynos_idle_driver, NULL);
	if (ret) {
		dev_err(&pdev->dev, "failed to register cpuidle driver\n");
		return ret;
	}

	return 0;
}

static struct platform_driver exynos_cpuidle_driver = {
	.probe	= exynos_cpuidle_probe,
	.driver = {
		.name = "exynos_cpuidle",
		.owner = THIS_MODULE,
	},
};

注册一个名为"exynos_cpuidle"的平台设备,会在probe函数中调用cpuidle_register函数注册该cpuidle的驱动。

调用cpuidle_select函数可以通过governor选择进入一个idle state中。

代码语言:javascript
复制
int cpuidle_select(struct cpuidle_driver *drv, struct cpuidle_device *dev)
{
	if (off || !initialized)
		return -ENODEV;

	if (!drv || !dev || !dev->enabled)
		return -EBUSY;

	if (unlikely(use_deepest_state))
		return cpuidle_find_deepest_state(drv, dev);

	return cpuidle_curr_governor->select(drv, dev);
}

此处会通过use_deepest_state变量判断,如果此值为enable,则调用cpuidle_find_deepest_state函数会选择一个最大的退出延迟的state。否则调用governor的select回调来选择。

代码语言:javascript
复制
static int cpuidle_find_deepest_state(struct cpuidle_driver *drv,
				      struct cpuidle_device *dev)
{
	unsigned int latency_req = 0;
	int i, ret = CPUIDLE_DRIVER_STATE_START - 1;

	for (i = CPUIDLE_DRIVER_STATE_START; i < drv->state_count; i++) {
		struct cpuidle_state *s = &drv->states[i];
		struct cpuidle_state_usage *su = &dev->states_usage[i];

		if (s->disabled || su->disable || s->exit_latency <= latency_req)
			continue;

		latency_req = s->exit_latency;
		ret = i;
	}
	return ret;
}

当选择好了一个idle state之后就会调用cpuidle_enter进入此idle state。最终调用cpuidle_enter_state函数。

代码语言:javascript
复制
int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv,
			int index)
{
	int entered_state;
	unsigned int broadcast;

	struct cpuidle_state *target_state = &drv->states[index];
	ktime_t time_start, time_end;
	s64 diff;

	trace_cpu_idle_rcuidle(index, dev->cpu);
	time_start = ktime_get();

	entered_state = target_state->enter(dev, drv, index);

	time_end = ktime_get();
	trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, dev->cpu);

	broadcast = drv->states[index].flags & CPUIDLE_FLAG_TIMER_STOP;
	if (broadcast)
		clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);

	if (!cpuidle_state_is_coupled(dev, drv, entered_state))
		local_irq_enable();

	diff = ktime_to_us(ktime_sub(time_end, time_start));
	if (diff > INT_MAX)
		diff = INT_MAX;

	dev->last_residency = (int) diff;

	if (entered_state >= 0) {
		/* Update cpuidle counters */
		/* This can be moved to within driver enter routine
		 * but that results in multiple copies of same code.
		 */
		dev->states_usage[entered_state].time += dev->last_residency;
		dev->states_usage[entered_state].usage++;
	} else {
		dev->last_residency = 0;
	}

	return entered_state;
}

1. 根据当前参数index,从驱动支持的states中选择一个cpuidle_state结构。

2. 调用此state的enter回调处理函数。进入此函数后此cpu就会进入idle状态,然后就不会再向下执行。如果此时手动按下power按键,则会唤醒cpu,继续执行。

3. 通过flag判断,当前state是否设置了CPUIDLE_FLAG_TIMER_STOP标志,如果设置则停止broadcast timer。

4. 统计进入idle状态的时间,延时等。

至于此函数cpuidle_enter是在哪里调用的呢? 搜索内核代码,发现在cpuidle_idle_call函数中实现了idle主要的进入功能。

此函数代码比较长,此处就不再贴出代码,简单说下流程。

1. 如果当前cpu需要任务去处理,则使能中断,并且退出。

代码语言:javascript
复制
	if (need_resched()) {
		local_irq_enable();
		return;
	}

2. 调用cpuidle_select函数,选择需要进入的idle_state。返回值next_state就是需要进入的idle state。

代码语言:javascript
复制
next_state = cpuidle_select(drv, dev);

3. 判断当前idle状态是否设置了TIMER_STOP标志。

代码语言:javascript
复制
broadcast = drv->states[next_state].flags & CPUIDLE_FLAG_TIMER_STOP;

4. 如果设置,则告诉timer framework,启动一个broadcast timer。

代码语言:javascript
复制
if (broadcast && clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu))

5. 设置idle的状态

代码语言:javascript
复制
idle_set_state(this_rq(), &drv->states[next_state]);

6. 进入idle状态

代码语言:javascript
复制
entered_state = cpuidle_enter(drv, dev, next_state);

进入此函数之后,如果没有唤醒,则就一直会睡眠下去。当有外部或者内部的唤醒,则会在此处醒来,继续执行。

7. 退出idle状态,使能中断。

代码语言:javascript
复制
if (WARN_ON_ONCE(irqs_disabled()))
	local_irq_enable(); 
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Linux Cpuidle Framework
  • 数据结构
  • 代码分析
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档