Linux cpuidle framework(2)_cpuidle core

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函数。

名称和描述

namedesc成员分别指定了idle状态的名称和描述。这些字符串用于在内核日志和其他输出中标识idle状态。

延迟和驻留时间

exit_latency_nstarget_residency_ns成员分别指定了idle状态的退出延迟和目标驻留时间,单位都是纳秒。退出延迟是指从idle状态唤醒到CPU完全退出idle状态所需的时间。目标驻留时间是指CPU在此idle状态必须停留的最小时间。

exit_latencytarget_residency成员分别指定了idle状态的退出延迟和目标驻留时间,单位都是微秒。这些成员是exit_latency_nstarget_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;
}

函数分析

  1. 检查参数的有效性。如果dev为NULL,则返回-EINVAL错误码。

  2. 获取CPU idle锁。

  3. 检查dev是否已经注册。如果已经注册,则直接返回-EBUSY错误码。

  4. 调用__cpuidle_device_init()函数初始化dev

  5. 调用__cpuidle_register_device()函数将dev注册到CPU idle设备列表中。

  6. 调用cpuidle_add_sysfs()函数为dev创建sysfs接口。

  7. 调用cpuidle_enable_device()函数使能dev

  8. 调用cpuidle_install_idle_handler()函数安装idle处理程序。

  9. 释放CPU idle锁。

  10. 返回注册的结果。

作用

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;
}

函数分析

  1. 检查参数的有效性。如果dev为NULL,则返回-EINVAL错误码。

  2. 检查dev是否已经使能。如果已经使能,则直接返回0。

  3. 检查当前idle调速器是否有效。如果当前idle调速器无效,则返回-EIO错误码。

  4. 获取CPU idle驱动程序。如果无法获取CPU idle驱动程序,则返回-EIO错误码。

  5. 检查dev是否已经注册。如果dev尚未注册,则返回-EINVAL错误码。

  6. dev添加到sysfs中。

  7. 调用当前idle调速器的enable()函数使能CPU idle设备。

  8. 设置dev->enabled标志为1,表示dev已经使能。

  9. 增加enabled_devices变量的值,表示使能的CPU idle设备数量增加了一个。

  10. 返回使能的结果。

作用

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_devicescpuidle_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_devicescpuidle_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;
}

函数分析

  1. 获取CPU idle驱动程序锁。

  2. 调用__cpuidle_register_driver()函数注册CPU idle驱动程序。

  3. 释放CPU idle驱动程序锁。

  4. 如果以下条件满足,则切换idle调速器:

    • 注册成功。
    • 没有指定idle调速器参数。
    • CPU idle驱动程序指定了首选idle调速器。
    • 当前idle调速器是刚注册的CPU idle驱动程序。
  5. 返回注册的结果。

作用

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;
}

函数分析

  1. 获取CPU idle锁。
  2. 遍历idle调速器列表,并检查每个idle调速器的名称是否与给定字符串匹配。
  3. 如果找到匹配的idle调速器,则返回该idle调速器。
  4. 释放CPU idle锁。
  5. 如果未找到匹配的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;
}

函数分析

  1. 获取CPU的设备。
  2. 获取设备的延迟要求。
  3. 获取全局延迟要求。
  4. 如果设备的延迟要求大于全局延迟要求,则将设备的延迟要求设置为全局延迟要求。
  5. 将延迟要求转换为纳秒。
  6. 返回延迟要求。

作用

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;
}

函数分析

  1. 检查参数的有效性。如果gov为NULL,则返回-EINVAL错误码。
  2. 如果gov是当前idle调速器,则直接返回0。
  3. 卸载idle处理程序。
  4. 如果当前idle调速器不为NULL,则禁用所有CPU idle设备。
  5. 设置当前idle调速器为gov
  6. 启用所有CPU idle设备。
  7. 安装idle处理程序。
  8. 打印一条消息,表明已切换到govidle调速器。
  9. 返回切换的结果。

作用

cpuidle_switch_governor()函数用于切换idle调速器。该函数会卸载idle处理程序,禁用所有CPU idle设备,设置当前idle调速器,启用所有CPU idle设备,安装idle处理程序,并打印一条消息,表明已切换到govidle调速器。

示例

以下是一个示例,展示了如何使用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;
}

函数分析

  1. 检查参数的有效性。如果gov为NULL或gov->select为NULL,则返回-EINVAL错误码。
  2. 如果CPU idle已禁用,则返回-ENODEV错误码。
  3. 获取CPU idle锁。
  4. 检查是否已经注册了具有相同名称的idle调速器。如果已经注册,则返回-EEXIST错误码。
  5. 将idle调速器添加到idle调速器列表中。
  6. 如果当前idle调速器为NULL,或者gov的名称与idle调速器参数相同,或者gov的评级高于当前idle调速器且gov的名称与idle调速器参数不同,则切换到govidle调速器。
  7. 释放CPU idle锁。
  8. 返回注册的结果。

作用

cpuidle_register_governor()函数用于注册一个idle调速器。该函数会将idle调速器添加到idle调速器列表中,并根据需要切换到govidle调速器。

示例

以下是一个示例,展示了如何使用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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值