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),如下:
include/linux/cpuidle.h
struct cpuidle_state {
char name[CPUIDLE_NAME_LEN];
char desc[CPUIDLE_DESC_LEN];
s64 exit_latency_ns;
s64 target_residency_ns;
unsigned int flags;
unsigned int exit_latency; /* in US */
int power_usage; /* in mW */
unsigned int target_residency; /* in US */
int (*enter) (struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index);
int (*enter_dead) (struct cpuidle_device *dev, int index);
/*
* CPUs execute ->enter_s2idle with the local tick or entire timekeeping
* suspended, so it must not re-enable interrupts at any point (even
* temporarily) or attempt to change states of clock event devices.
*
* This callback may point to the same function as ->enter if all of
* the above requirements are met by it.
*/
int (*enter_s2idle)(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index);
};
struct cpuidle_state
结构体定义了CPU idle状态的各种属性,包括名称、描述、进入延迟、目标驻留时间、标志、退出延迟、功耗、目标驻留时间、进入函数、进入死循环函数和进入S2idle函数。
名称和描述
name
和desc
成员分别指定了idle状态的名称和描述。这些字符串用于在内核日志和其他输出中标识idle状态。
延迟和驻留时间
exit_latency_ns
和target_residency_ns
成员分别指定了idle状态的退出延迟和目标驻留时间,单位都是纳秒。退出延迟是指从idle状态唤醒到CPU完全退出idle状态所需的时间。目标驻留时间是指CPU在此idle状态必须停留的最小时间。
exit_latency
和target_residency
成员分别指定了idle状态的退出延迟和目标驻留时间,单位都是微秒。这些成员是exit_latency_ns
和target_residency_ns
成员的别名,便于使用微秒作为单位。
标志
flags
成员是一个无符号整数,用于指定idle状态的各种标志。这些标志定义在<linux/cpuidle.h>
头文件中。
功耗
power_usage
成员指定了idle状态的功耗,单位是毫瓦。此成员可用于估计CPU在idle状态下的功耗。
进入函数
enter
成员是一个函数指针,指向idle状态的进入函数。此函数负责将CPU置于idle状态。
进入死循环函数
enter_dead
成员是一个函数指针,指向idle状态的进入死循环函数。此函数与enter
函数类似,但它不会返回。当CPU进入死循环idle状态时,此函数将被调用。
进入S2idle函数
enter_s2idle
成员是一个函数指针,指向idle状态的进入S2idle函数。此函数与enter
函数类似,但它适用于S2idle状态。S2idle状态是一种特殊的idle状态,其中CPU的本地定时器被停止。
结构体的作用
struct cpuidle_state
结构体用于描述CPU idle状态的各种属性。这些属性用于选择要进入的idle状态,并用于计算CPU在idle状态下的功耗。
CPU idle状态管理器使用struct cpuidle_state
结构体来管理CPU idle状态。CPU idle状态管理器根据idle状态的属性选择要进入的idle状态,并负责在idle状态之间进行切换。
2.2 cpuidle_device
现实中,并没有“cpuidle device”这样一个真实的设备,因此cpuidle device是一个虚拟设备,我们可以把它类比为“cpu idle controller”,负责实现cpuidle相关的逻辑。在多核CPU中,每个CPU core,都会对应一个cpuidle device。Linux kernel使用struct cpuidle_device抽象cpuidle device,如下:
#include/linux/cpuidle.h
struct cpuidle_device {
unsigned int registered:1;
unsigned int enabled:1;
unsigned int poll_time_limit:1;
unsigned int cpu;
ktime_t next_hrtimer;
int last_state_idx;
u64 last_residency_ns;
u64 poll_limit_ns;
u64 forced_idle_latency_limit_ns;
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
cpumask_t coupled_cpus;
struct cpuidle_coupled *coupled;
#endif
};
struct cpuidle_device
结构体定义了CPU idle设备的各种属性,包括CPU编号、下一个高分辨率时钟时间、上次进入的idle状态索引、上次驻留时间、轮询时间限制、强制idle延迟时间限制、idle状态使用情况、内核对象、驱动程序内核对象和设备内核对象。
CPU编号
cpu
成员指定了此CPU idle设备对应的CPU编号。
下一个高分辨率时钟时间
next_hrtimer
成员指定了下一个高分辨率时钟时间,单位是纳秒。此时间用于唤醒CPU idle设备,以检查idle状态是否已经结束。
上次进入的idle状态索引
last_state_idx
成员指定了上次进入的idle状态的索引。此信息用于跟踪CPU idle设备的idle状态历史记录。
上次驻留时间
last_residency_ns
成员指定了上次驻留时间,单位是纳秒。此信息用于计算CPU idle设备的平均驻留时间。
轮询时间限制
poll_limit_ns
成员指定了轮询时间限制,单位是纳秒。此限制用于防止CPU idle设备无限期地轮询idle状态。
强制idle延迟时间限制
forced_idle_latency_limit_ns
成员指定了强制idle延迟时间限制,单位是纳秒。此限制用于防止CPU idle设备在idle状态停留太长时间。
idle状态使用情况
states_usage
成员是一个数组,用于存储idle状态的使用情况。此数组的每个元素对应一个idle状态,并包含该idle状态的使用次数和平均驻留时间。
内核对象
kobjs
成员是一个数组,用于存储idle状态的内核对象。此数组的每个元素对应一个idle状态,并包含该idle状态的内核对象。
驱动程序内核对象
kobj_driver
成员是一个内核对象,用于存储CPU idle驱动程序的内核对象。
设备内核对象
kobj_dev
成员是一个内核对象,用于存储CPU idle设备的内核对象。
设备列表
device_list
成员是一个链表,用于将此CPU idle设备链接到所有CPU idle设备的链表中。
作用和功能
struct cpuidle_device
结构体用于描述CPU idle设备的各种属性。这些属性用于管理CPU idle设备,并用于计算CPU idle设备的idle状态使用情况和平均驻留时间。
CPU idle状态管理器使用struct cpuidle_device
结构体来管理CPU idle设备。CPU idle状态管理器根据CPU idle设备的属性选择要进入的idle状态,并负责在idle状态之间进行切换。
CPU idle设备还具有轮询功能。CPU idle设备会定期轮询idle状态,以检查idle状态是否已经结束。如果idle状态已经结束,CPU idle设备会唤醒CPU。
CPU idle设备还具有强制idle延迟时间限制功能。当CPU idle设备在idle状态停留超过强制idle延迟时间限制时,CPU idle设备会强制CPU退出idle状态。
CPU idle设备还具有idle状态使用情况统计功能。CPU idle设备会统计每个idle状态的使用次数和平均驻留时间。这些信息可以用于分析CPU idle设备的使用情况,并用于改进CPU idle状态管理算法。
2.3 cpuidle_driver
cpuidle core使用struct cpuidle_driver抽象cpuidle驱动,如下:
/****************************
* CPUIDLE DRIVER INTERFACE *
****************************/
struct cpuidle_driver {
const char *name;
struct module *owner;
/* 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;
/* preferred governor to switch at register time */
const char *governor;
};
struct cpuidle_driver
结构体定义了CPU idle驱动程序的各种属性,包括名称、所有者模块、是否使用广播定时器、idle状态数组、idle状态数量、安全状态索引、CPU掩码和首选调速器。
名称
name
成员指定了CPU idle驱动程序的名称。此名称用于在内核日志和其他输出中标识CPU idle驱动程序。
所有者模块
owner
成员指定了CPU idle驱动程序的所有者模块。此模块通常是CPU idle驱动程序所在的内核模块。
是否使用广播定时器
bctimer
成员是一个标志,用于指定CPU idle驱动程序是否使用广播定时器。广播定时器用于在所有CPU上同时唤醒CPU idle设备。
idle状态数组
states
成员是一个数组,用于存储idle状态。此数组的每个元素对应一个idle状态,并包含该idle状态的各种属性,如名称、描述、进入延迟、目标驻留时间、标志、退出延迟、功耗和进入函数。
idle状态数量
state_count
成员指定了idle状态的数量。
安全状态索引
safe_state_index
成员指定了安全状态的索引。安全状态是当CPU idle设备无法进入其他idle状态时进入的idle状态。
CPU掩码
cpumask
成员是一个CPU掩码,用于指定CPU idle驱动程序支持的CPU。
首选调速器
governor
成员指定了CPU idle驱动程序的首选调速器。调速器是一种内核组件,用于管理CPU频率和电压。
作用和功能
struct cpuidle_driver
结构体用于描述CPU idle驱动程序的各种属性。这些属性用于选择要进入的idle状态,并用于在idle状态之间进行切换。
CPU idle状态管理器使用struct cpuidle_driver
结构体来管理CPU idle驱动程序。CPU idle状态管理器根据CPU idle驱动程序的属性选择要进入的idle状态,并负责在idle状态之间进行切换。
CPU idle驱动程序还具有安全状态功能。当CPU idle设备无法进入其他idle状态时,CPU idle驱动程序会进入安全状态。
CPU idle驱动程序还具有首选调速器功能。当CPU idle设备进入idle状态时,CPU idle驱动程序会将CPU频率和电压切换到首选调速器指定的设置。
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_register_device - registers a CPU's idle PM feature
* @dev: the cpu
*/
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;
}
EXPORT_SYMBOL_GPL(cpuidle_register_device);
cpuidle_register_device()`函数用于注册一个CPU的idle PM特性。
函数原型
int cpuidle_register_device(struct cpuidle_device *dev);
参数
dev
: 要注册的CPU idle设备。
返回值
- 成功:0
- 失败:负错误码
函数体
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;
}
函数分析
-
检查参数的有效性。如果
dev
为NULL,则返回-EINVAL
错误码。 -
获取CPU idle锁。
-
检查
dev
是否已经注册。如果已经注册,则直接返回-EBUSY
错误码。 -
调用
__cpuidle_device_init()
函数初始化dev
。 -
调用
__cpuidle_register_device()
函数将dev
注册到CPU idle设备列表中。 -
调用
cpuidle_add_sysfs()
函数为dev
创建sysfs接口。 -
调用
cpuidle_enable_device()
函数使能dev
。 -
调用
cpuidle_install_idle_handler()
函数安装idle处理程序。 -
释放CPU idle锁。
-
返回注册的结果。
作用
cpuidle_register_device()
函数用于注册一个CPU的idle PM特性。该函数会将CPU idle设备添加到CPU idle设备列表中,并为其创建sysfs接口。此外,该函数还会使能CPU idle设备并安装idle处理程序。
示例
以下是一个示例,展示了如何使用cpuidle_register_device()
函数注册一个CPU idle设备:
struct cpuidle_device dev;
/* Initialize the CPU idle device. */
/* Register the CPU idle device. */
ret = cpuidle_register_device(&dev);
if (ret) {
/* Handle the error. */
}
2)cpuidle_enable_device
我们通过cpuidle_enable_device接口,来理解一下何为idle device的enable?
/**
* cpuidle_enable_device - enables idle PM for a CPU
* @dev: the CPU
*
* This function must be called between cpuidle_pause_and_lock and
* cpuidle_resume_and_unlock when used externally.
*/
int cpuidle_enable_device(struct cpuidle_device *dev)
{
int ret;
struct cpuidle_driver *drv;
if (!dev)
return -EINVAL;
if (dev->enabled)
return 0;
if (!cpuidle_curr_governor)
return -EIO;
drv = cpuidle_get_cpu_driver(dev);
if (!drv)
return -EIO;
if (!dev->registered)
return -EINVAL;
ret = cpuidle_add_device_sysfs(dev);
if (ret)
return ret;
if (cpuidle_curr_governor->enable) {
ret = cpuidle_curr_governor->enable(drv, dev);
if (ret)
goto fail_sysfs;
}
smp_wmb();
dev->enabled = 1;
enabled_devices++;
return 0;
fail_sysfs:
cpuidle_remove_device_sysfs(dev);
return ret;
}
EXPORT_SYMBOL_GPL(cpuidle_enable_device);
cpuidle_enable_device()
函数用于使能一个CPU的idle PM特性。
函数原型
int cpuidle_enable_device(struct cpuidle_device *dev);
参数
dev
: 要使能的CPU idle设备。
返回值
- 成功:0
- 失败:负错误码
函数体
int cpuidle_enable_device(struct cpuidle_device *dev)
{
int ret;
struct cpuidle_driver *drv;
if (!dev)
return -EINVAL;
if (dev->enabled)
return 0;
if (!cpuidle_curr_governor)
return -EIO;
drv = cpuidle_get_cpu_driver(dev);
if (!drv)
return -EIO;
if (!dev->registered)
return -EINVAL;
ret = cpuidle_add_device_sysfs(dev);
if (ret)
return ret;
if (cpuidle_curr_governor->enable) {
ret = cpuidle_curr_governor->enable(drv, dev);
if (ret)
goto fail_sysfs;
}
smp_wmb();
dev->enabled = 1;
enabled_devices++;
return 0;
fail_sysfs:
cpuidle_remove_device_sysfs(dev);
return ret;
}
函数分析
-
检查参数的有效性。如果
dev
为NULL,则返回-EINVAL
错误码。 -
检查
dev
是否已经使能。如果已经使能,则直接返回0。 -
检查当前idle调速器是否有效。如果当前idle调速器无效,则返回
-EIO
错误码。 -
获取CPU idle驱动程序。如果无法获取CPU idle驱动程序,则返回
-EIO
错误码。 -
检查
dev
是否已经注册。如果dev
尚未注册,则返回-EINVAL
错误码。 -
将
dev
添加到sysfs中。 -
调用当前idle调速器的
enable()
函数使能CPU idle设备。 -
设置
dev->enabled
标志为1,表示dev
已经使能。 -
增加
enabled_devices
变量的值,表示使能的CPU idle设备数量增加了一个。 -
返回使能的结果。
作用
cpuidle_enable_device()
函数用于使能一个CPU的idle PM特性。该函数会将CPU idle设备添加到sysfs中,并调用当前idle调速器的enable()
函数使能CPU idle设备。
示例
以下是一个示例,展示了如何使用cpuidle_enable_device()
函数使能一个CPU idle设备:
struct cpuidle_device dev;
/* Initialize the CPU idle device. */
/* Register the CPU idle device. */
/* Enable the CPU idle device. */
ret = cpuidle_enable_device(&dev);
if (ret) {
/* Handle the error. */
}
cpuidle_unregister_device和cpuidle_disable_device为相反动作,不再细说。
注1:有关per-CPU变量
由前面的描述可知,在多核系统中,每个CPU会对应一个cpuidle device,因此cpuidle_register_device每被执行一次,就会注册一个不同的设备。如果cpuidle需要将它们分别记录下来,就要借助per-CPU变量,以cpuidle_devices指针为例,其声明和定义分别如下:
DECLARE_PER_CPU(struct cpuidle_device *, cpuidle_devices);
DECLARE_PER_CPU(struct cpuidle_device, cpuidle_dev);
这两行代码声明了两个per-CPU变量:cpuidle_devices
和cpuidle_dev
。
cpuidle_devices
cpuidle_devices
是一个指向CPU idle设备指针的数组。该数组的每个元素对应一个CPU,并包含该CPU的CPU idle设备。
cpuidle_dev
cpuidle_dev
是一个CPU idle设备。该变量包含当前CPU的CPU idle设备。
作用
这两个per-CPU变量用于管理CPU idle设备。CPU idle状态管理器使用cpuidle_devices
数组来管理所有CPU的CPU idle设备。CPU idle设备使用cpuidle_dev
变量来管理当前CPU的CPU idle设备。
示例
以下是一个示例,展示了如何使用cpuidle_devices
和cpuidle_dev
变量:
/* Get the CPU idle device for the current CPU. */
struct cpuidle_device *dev = get_cpu_idle_device();
/* Do something with the CPU idle device. */
put_cpu_idle_device(dev);
在这个示例中,get_cpu_idle_device()
函数用于获取当前CPU的CPU idle设备。然后,put_cpu_idle_device()
函数用于释放CPU idle设备。
3.2 cpuidle driver管理
cpuidle driver管理位于drivers/cpuidle/driver.c中,主要负责cpuidle driver的注册、获取等逻辑的实现。
cpuidle_register_driver用于注册一个cpuidle driver,它主要完成如下内容
/**
* cpuidle_register_driver - registers a driver
* @drv: a pointer to a valid struct cpuidle_driver
*
* Register the driver under a lock to prevent concurrent attempts to
* [un]register the driver from occuring at the same time.
*
* Returns 0 on success, a negative error code (returned by
* __cpuidle_register_driver()) otherwise.
*/
int cpuidle_register_driver(struct cpuidle_driver *drv)
{
struct cpuidle_governor *gov;
int ret;
spin_lock(&cpuidle_driver_lock);
ret = __cpuidle_register_driver(drv);
spin_unlock(&cpuidle_driver_lock);
if (!ret && !strlen(param_governor) && drv->governor &&
(cpuidle_get_driver() == drv)) {
mutex_lock(&cpuidle_lock);
gov = cpuidle_find_governor(drv->governor);
if (gov) {
cpuidle_prev_governor = cpuidle_curr_governor;
if (cpuidle_switch_governor(gov) < 0)
cpuidle_prev_governor = NULL;
}
mutex_unlock(&cpuidle_lock);
}
return ret;
}
EXPORT_SYMBOL_GPL(cpuidle_register_driver);
cpuidle_register_driver()
函数用于注册一个CPU idle驱动程序。
函数原型
int cpuidle_register_driver(struct cpuidle_driver *drv);
参数
drv
: 要注册的CPU idle驱动程序。
返回值
- 成功:0
- 失败:负错误码
函数体
int cpuidle_register_driver(struct cpuidle_driver *drv)
{
struct cpuidle_governor *gov;
int ret;
spin_lock(&cpuidle_driver_lock);
ret = __cpuidle_register_driver(drv);
spin_unlock(&cpuidle_driver_lock);
if (!ret && !strlen(param_governor) && drv->governor &&
(cpuidle_get_driver() == drv)) {
mutex_lock(&cpuidle_lock);
gov = cpuidle_find_governor(drv->governor);
if (gov) {
cpuidle_prev_governor = cpuidle_curr_governor;
if (cpuidle_switch_governor(gov) < 0)
cpuidle_prev_governor = NULL;
}
mutex_unlock(&cpuidle_lock);
}
return ret;
}
函数分析
-
获取CPU idle驱动程序锁。
-
调用
__cpuidle_register_driver()
函数注册CPU idle驱动程序。 -
释放CPU idle驱动程序锁。
-
如果以下条件满足,则切换idle调速器:
- 注册成功。
- 没有指定idle调速器参数。
- CPU idle驱动程序指定了首选idle调速器。
- 当前idle调速器是刚注册的CPU idle驱动程序。
-
返回注册的结果。
作用
cpuidle_register_driver()
函数用于注册一个CPU idle驱动程序。该函数会将CPU idle驱动程序添加到CPU idle驱动程序列表中,并根据需要切换idle调速器。
示例
以下是一个示例,展示了如何使用cpuidle_register_driver()
函数注册一个CPU idle驱动程序:
struct cpuidle_driver drv;
/* Initialize the CPU idle driver. */
/* Register the CPU idle driver. */
ret = cpuidle_register_driver(&drv);
if (ret) {
/* Handle the error. */
}
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的获取,就是返回这个指针的值。如下:
static struct cpuidle_driver *cpuidle_curr_driver;
/**
* __cpuidle_get_cpu_driver - return the global cpuidle driver pointer.
* @cpu: ignored without the multiple driver support
*
* Return a pointer to a struct cpuidle_driver object or NULL if no driver was
* previously registered.
*/
static inline struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu)
{
return cpuidle_curr_driver;
}
/**
* __cpuidle_set_driver - assign the global cpuidle driver variable.
* @drv: pointer to a struct cpuidle_driver object
*
* Returns 0 on success, -EBUSY if the driver is already registered.
*/
static inline int __cpuidle_set_driver(struct cpuidle_driver *drv)
{
if (cpuidle_curr_driver)
return -EBUSY;
cpuidle_curr_driver = drv;
return 0;
}
/**
* __cpuidle_unset_driver - unset the global cpuidle driver variable.
* @drv: a pointer to a struct cpuidle_driver
*
* Reset the global cpuidle variable to NULL. If @drv does not match the
* registered driver, do nothing.
*/
static inline void __cpuidle_unset_driver(struct cpuidle_driver *drv)
{
if (drv == cpuidle_curr_driver)
cpuidle_curr_driver = NULL;
}
cpuidle_curr_driver
是一个指向当前CPU idle驱动程序的指针。
static struct cpuidle_driver *cpuidle_curr_driver;
__cpuidle_get_cpu_driver()
__cpuidle_get_cpu_driver()
函数返回当前CPU idle驱动程序的指针。
函数原型
static inline struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu);
参数
cpu
: CPU编号(在多驱动程序支持的情况下使用)
返回值
- 当前CPU idle驱动程序的指针,或NULL(如果尚未注册驱动程序)
函数体
static inline struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu)
{
return cpuidle_curr_driver;
}
__cpuidle_set_driver()
__cpuidle_set_driver()
函数设置当前CPU idle驱动程序的指针。
函数原型
static inline int __cpuidle_set_driver(struct cpuidle_driver *drv);
参数
drv
: 要设置的CPU idle驱动程序
返回值
- 成功:0
- 失败:-EBUSY(如果已经注册了驱动程序)
函数体
static inline int __cpuidle_set_driver(struct cpuidle_driver *drv)
{
if (cpuidle_curr_driver)
return -EBUSY;
cpuidle_curr_driver = drv;
return 0;
}
__cpuidle_unset_driver()
__cpuidle_unset_driver()
函数取消设置当前CPU idle驱动程序的指针。
函数原型
static inline void __cpuidle_unset_driver(struct cpuidle_driver *drv);
参数
drv
: 要取消设置的CPU idle驱动程序
返回值
- 无
函数体
static inline void __cpuidle_unset_driver(struct cpuidle_driver *drv)
{
if (drv == cpuidle_curr_driver)
cpuidle_curr_driver = NULL;
}
作用
这些函数用于管理当前CPU idle驱动程序的指针。CPU idle状态管理器使用这些函数来获取、设置和取消设置当前CPU idle驱动程序的指针。
示例
以下是一个示例,展示了如何使用这些函数来管理当前CPU idle驱动程序的指针:
/* Get the current CPU idle driver. */
struct cpuidle_driver *drv = __cpuidle_get_cpu_driver(0);
/* Set the current CPU idle driver. */
ret = __cpuidle_set_driver(drv);
if (ret) {
/* Handle the error. */
}
/* Unset the current CPU idle driver. */
__cpuidle_unset_driver(drv);
另外,如果使能这个配置项,说明不同CPU的idle功能不同,每个CPU都要有一个driver。此时cpuidle idle的注册,又要依赖per cpu变量,就是将当前的注册的driver,保存在对应cpu的per cpu指针中。同理,cpuidle driver的获取,就是返回per cpu指针的值。如下:
static DEFINE_PER_CPU(struct cpuidle_driver *, cpuidle_drivers);
/**
* __cpuidle_get_cpu_driver - return the cpuidle driver tied to a CPU.
* @cpu: the CPU handled by the driver
*
* Returns a pointer to struct cpuidle_driver or NULL if no driver has been
* registered for @cpu.
*/
static struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu)
{
return per_cpu(cpuidle_drivers, cpu);
}
/**
* __cpuidle_unset_driver - unset per CPU driver variables.
* @drv: a valid pointer to a struct cpuidle_driver
*
* For each CPU in the driver's CPU mask, unset the registered driver per CPU
* variable. If @drv is different from the registered driver, the corresponding
* variable is not cleared.
*/
static inline void __cpuidle_unset_driver(struct cpuidle_driver *drv)
{
int cpu;
for_each_cpu(cpu, drv->cpumask) {
if (drv != __cpuidle_get_cpu_driver(cpu))
continue;
per_cpu(cpuidle_drivers, cpu) = NULL;
}
}
/**
* __cpuidle_set_driver - set per CPU driver variables for the given driver.
* @drv: a valid pointer to a struct cpuidle_driver
*
* Returns 0 on success, -EBUSY if any CPU in the cpumask have a driver
* different from drv already.
*/
static inline int __cpuidle_set_driver(struct cpuidle_driver *drv)
{
int cpu;
for_each_cpu(cpu, drv->cpumask) {
struct cpuidle_driver *old_drv;
old_drv = __cpuidle_get_cpu_driver(cpu);
if (old_drv && old_drv != drv)
return -EBUSY;
}
for_each_cpu(cpu, drv->cpumask)
per_cpu(cpuidle_drivers, cpu) = drv;
return 0;
}
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,如下:
static int __cpuidle poll_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
u64 time_start = local_clock();
dev->poll_time_limit = false;
local_irq_enable();
if (!current_set_polling_and_test()) {
unsigned int loop_count = 0;
u64 limit;
limit = cpuidle_poll_time(drv, dev);
while (!need_resched()) {
cpu_relax();
if (loop_count++ < POLL_IDLE_RELAX_COUNT)
continue;
loop_count = 0;
if (local_clock() - time_start > limit) {
dev->poll_time_limit = true;
break;
}
}
}
current_clr_polling();
return index;
}
void cpuidle_poll_state_init(struct cpuidle_driver *drv)
{
struct cpuidle_state *state = &drv->states[0];
snprintf(state->name, CPUIDLE_NAME_LEN, "POLL");
snprintf(state->desc, CPUIDLE_DESC_LEN, "CPUIDLE CORE POLL IDLE");
state->exit_latency = 0;
state->target_residency = 0;
state->exit_latency_ns = 0;
state->target_residency_ns = 0;
state->power_usage = -1;
state->enter = poll_idle;
state->flags = CPUIDLE_FLAG_POLLING;
}
EXPORT_SYMBOL_GPL(cpuidle_poll_state_init);
3.3 cpuidle governor管理
governor管理位于drivers/cpuidle/governor.c中,包括governor的注册、获取和切换功能,分别由下面三个接口实现:
cpuidle_find_governor()
函数用于查找具有指定名称的idle调速器。
函数原型
struct cpuidle_governor *cpuidle_find_governor(const char *str);
参数
str
: 要查找的idle调速器的名称。
返回值
- 找到的idle调速器,或NULL(如果未找到)
函数体
struct cpuidle_governor *cpuidle_find_governor(const char *str)
{
struct cpuidle_governor *gov;
list_for_each_entry(gov, &cpuidle_governors, governor_list)
if (!strncasecmp(str, gov->name, CPUIDLE_NAME_LEN))
return gov;
return NULL;
}
函数分析
- 获取CPU idle锁。
- 遍历idle调速器列表,并检查每个idle调速器的名称是否与给定字符串匹配。
- 如果找到匹配的idle调速器,则返回该idle调速器。
- 释放CPU idle锁。
- 如果未找到匹配的idle调速器,则返回NULL。
作用
cpuidle_find_governor()
函数用于查找具有指定名称的idle调速器。该函数会遍历idle调速器列表,并检查每个idle调速器的名称是否与给定字符串匹配。如果找到匹配的idle调速器,则返回该idle调速器。否则,返回NULL。
示例
以下是一个示例,展示了如何使用cpuidle_find_governor()
函数查找一个idle调速器:
struct cpuidle_governor *gov;
/* Get the idle governor name. */
/* Find the idle governor. */
gov = cpuidle_find_governor(name);
if (gov) {
/* Do something with the idle governor. */
} else {
/* Handle the error. */
}
cpuidle_governor_latency_req()
cpuidle_governor_latency_req()
函数用于计算CPU的延迟约束。
函数原型
s64 cpuidle_governor_latency_req(unsigned int cpu);
参数
cpu
: 目标CPU。
返回值
- CPU的延迟约束(以纳秒为单位)。
函数体
s64 cpuidle_governor_latency_req(unsigned int cpu)
{
struct device *device = get_cpu_device(cpu);
int device_req = dev_pm_qos_raw_resume_latency(device);
int global_req = cpu_latency_qos_limit();
if (device_req > global_req)
device_req = global_req;
return (s64)device_req * NSEC_PER_USEC;
}
函数分析
- 获取CPU的设备。
- 获取设备的延迟要求。
- 获取全局延迟要求。
- 如果设备的延迟要求大于全局延迟要求,则将设备的延迟要求设置为全局延迟要求。
- 将延迟要求转换为纳秒。
- 返回延迟要求。
作用
cpuidle_governor_latency_req()
函数用于计算CPU的延迟约束。该函数会获取设备的延迟要求,全局延迟要求,并将延迟要求转换为纳秒。
示例
以下是一个示例,展示了如何使用cpuidle_governor_latency_req()
函数计算CPU的延迟约束:
unsigned int cpu = 0;
s64 latency_req;
/* Get the CPU's latency requirement. */
latency_req = cpuidle_governor_latency_req(cpu);
/* Do something with the latency requirement. */
cpuidle_switch_governor()
函数用于切换idle调速器。
函数原型
int cpuidle_switch_governor(struct cpuidle_governor *gov);
参数
gov
: 要切换到的idle调速器。
返回值
- 成功:0
- 失败:负错误码
函数体
int cpuidle_switch_governor(struct cpuidle_governor *gov)
{
struct cpuidle_device *dev;
if (!gov)
return -EINVAL;
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);
}
cpuidle_curr_governor = gov;
list_for_each_entry(dev, &cpuidle_detected_devices, device_list)
cpuidle_enable_device(dev);
cpuidle_install_idle_handler();
pr_info("cpuidle: using governor %s\n", gov->name);
return 0;
}
函数分析
- 检查参数的有效性。如果
gov
为NULL,则返回-EINVAL
错误码。 - 如果
gov
是当前idle调速器,则直接返回0。 - 卸载idle处理程序。
- 如果当前idle调速器不为NULL,则禁用所有CPU idle设备。
- 设置当前idle调速器为
gov
。 - 启用所有CPU idle设备。
- 安装idle处理程序。
- 打印一条消息,表明已切换到
gov
idle调速器。 - 返回切换的结果。
作用
cpuidle_switch_governor()
函数用于切换idle调速器。该函数会卸载idle处理程序,禁用所有CPU idle设备,设置当前idle调速器,启用所有CPU idle设备,安装idle处理程序,并打印一条消息,表明已切换到gov
idle调速器。
示例
以下是一个示例,展示了如何使用cpuidle_switch_governor()
函数切换idle调速器:
struct cpuidle_governor *gov;
/* Find the idle governor. */
/* Switch to the idle governor. */
ret = cpuidle_switch_governor(gov);
if (ret) {
/* Handle the error. */
}
cpuidle_register_governor()
cpuidle_register_governor()
函数用于注册一个idle调速器。
函数原型
int cpuidle_register_governor(struct cpuidle_governor *gov);
参数
gov
: 要注册的idle调速器。
返回值
- 成功:0
- 失败:负错误码
函数体
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 ||
!strncasecmp(param_governor, gov->name, CPUIDLE_NAME_LEN) ||
(cpuidle_curr_governor->rating < gov->rating &&
strncasecmp(param_governor, cpuidle_curr_governor->name,
CPUIDLE_NAME_LEN)))
cpuidle_switch_governor(gov);
}
mutex_unlock(&cpuidle_lock);
return ret;
}
函数分析
- 检查参数的有效性。如果
gov
为NULL或gov->select
为NULL,则返回-EINVAL
错误码。 - 如果CPU idle已禁用,则返回
-ENODEV
错误码。 - 获取CPU idle锁。
- 检查是否已经注册了具有相同名称的idle调速器。如果已经注册,则返回
-EEXIST
错误码。 - 将idle调速器添加到idle调速器列表中。
- 如果当前idle调速器为NULL,或者
gov
的名称与idle调速器参数相同,或者gov
的评级高于当前idle调速器且gov
的名称与idle调速器参数不同,则切换到gov
idle调速器。 - 释放CPU idle锁。
- 返回注册的结果。
作用
cpuidle_register_governor()
函数用于注册一个idle调速器。该函数会将idle调速器添加到idle调速器列表中,并根据需要切换到gov
idle调速器。
示例
以下是一个示例,展示了如何使用cpuidle_register_governor()
函数注册一个idle调速器:
struct cpuidle_governor gov;
/* Initialize the idle governor. */
/* Register the idle governor. */
ret = cpuidle_register_governor(&gov);
if (ret) {
/* Handle the error. */
}
3.4 cpuidle整体的管理功能
整体的管理功能位于drivers/cpuidle/cpuidle.c中,负责如下事项:
/**
* cpuidle_init - core initializer
*/
static int __init cpuidle_init(void)
{
// 检查 cpuidle 是否被禁用,如果禁用则返回错误码 -ENODEV
if (cpuidle_disabled())
return -ENODEV;
// 将 cpuidle 接口添加到 CPU 子系统的设备根目录中
return cpuidle_add_interface(cpu_subsys.dev_root);
}
// 定义模块参数 "off",类型为整数,访问权限为只读(0444)
module_param(off, int, 0444);
// 定义模块参数 "governor",类型为字符串,数组大小为 CPUIDLE_NAME_LEN,访问权限为只读(0444)
module_param_string(governor, param_governor, CPUIDLE_NAME_LEN, 0444);
// 在内核初始化时调用 cpuidle_init 函数,作为核心初始化调用
core_initcall(cpuidle_init);
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