转:https://zhuanlan.zhihu.com/p/539722367
本文基于以下软硬件假定:

架构:AARCH64

内核版本:5.14.0-rc51

atf版本:Arm trust firmware V2.5

1 cpu idle目标
  cpu是计算机系统的核心,其主要功能是执行程序。它就像流水线的工人一样,按照固定节拍,重复着取指、译码及指令执行操作。而当cpu上没有可执行任务时,就会进入空闲状态,那么该状态下cpu应该如何运行呢?

写过单片机程序的同学一定对下面这段代码很熟悉:

while (1) {
	if (condition)
		break;
}

该程序判断某个条件是否成立,若不成立则持续执行死循环操作,这也是最简单的idle处理方式。但是cpu的运行是需要消耗能量的,对于像智能手机之类的移动设备,cpu空转就会直接影响到其续航能力。

为此,cpu需要提供更加丰富的低功耗管理模式,如arm64架构提供了一条wfi指令。cpu一旦执行该指令就会进入低功耗状态,该状态会关闭cpu时钟,从而可降低其动态功耗。当cpu接收到中断之后,就又会被唤醒而重新投入运行。

随着人们对设备功耗要求的不断提高,也对cpu idle状态的管理提出了更高的要求。如其在idle时是否可以进入更深的睡眠状态,对于包含cluster的拓扑,在一个cluster中所有cpu都进入idle状态时,是否应该将cluster本身也设置为idle状态等。

由于不同cpu架构睡眠状态的实现方式不同,因此内核需要为其提供相应的驱动程序。而由于功耗管理最终需要操作具体cpu相关的硬件,故内核又为每个cpu抽象出一个对应的cpuidle设备。

更进一步,对于cpu来说,其睡眠状态越深,则相应的功耗就越低。但是任何事物都是有代价的,更深的睡眠状态就意味更高的唤醒延迟。因此,内核需要评估在不同场景下,cpu应该要进入哪种睡眠状态。

显然这不是一件容易的事情,为此内核为其提供了一套管理策略相关的框架cpu idle governor。不同的管理策略可以实现为不同governor,这些governor会有不同的评分(rating)。cpuidle驱动可以指定使用某个特定的governor,但若未指定,则在驱动注册时比较系统中所有已注册governor的rating值,为其选择一个分值最高的governor。

2 cpu idle数据结构
2.1 cpu idle设备
  由于cpu idle的管理对象为cpu,且每个cpu都可以独立控制自己的idle状态。为了方便对这些实例的管理,内核为每个cpu抽象出了一个虚拟的cpuidle设备。该设备的主要功能是提供idle管理相关的信息和统计数据,其定义如下:

struct cpuidle_device {
	unsigned int		registered:1;1unsigned int		enabled:1;2unsigned int		poll_time_limit:1;3unsigned int		cpu;
	ktime_t			next_hrtimer;4int			last_state_idx;5)
	u64			last_residency_ns;6)
	u64			poll_limit_ns;7)
	u64			forced_idle_latency_limit_ns;                        
	struct cpuidle_state_usage	states_usage[CPUIDLE_STATE_MAX];8struct cpuidle_state_kobj *kobjs[CPUIDLE_STATE_MAX];9struct 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;10#endif
};

(1)该cpu idle设备是否已经注册

(2)该cpuidle设备是否已使能

(3)cpu的tick中断用于驱动调度器运转,正常情况该中断会以HZ为频率周期性地触发。当cpu进入idle睡眠状态时,实际上调度器中已经没有可运行任务,因此该中断在此时并没有什么作用。

更糟糕的是对于wfi之类的低功耗状态,还会被该定时器周期性地唤醒。使得idle cpu不停地处于唤醒->睡眠->唤醒->睡眠的循环中,从而显著增加cpu功耗。为了解决该问题内核引入了tick时钟的nohz模式,即在cpu进入idle时可关闭其对应的tick时钟。

但是有时idle时间很短,如可能低于一个tick周期,此时就没必要进入睡眠状态和关闭tick时钟,故cpu idle模块只需采用简单轮询方式即可。还有像haltpoll之类的governor,本身就是通过轮询方式实现idle流程的。这些情形都需要使用该参数表示轮询时间是否已到期

(4)由于cpu的预期空闲时间,是governor选择睡眠状态的重要参考依据,预期空闲时间越长,可以采用的睡眠状态就越深。一般来说定时器到期时,表明cpu上有事件要处理,其也可能需要退出空闲状态执行相应的任务。因此governor在计算预期空闲时间时,会参考下一次到期定时器的时间

(5)上一次进入idle时对应睡眠状态的index

(6)该设备上一次停留在idle状态的时间,单位为ns

(7)当采用轮询方式时,该值用于指定最大的轮询时间

(8)该参数用于保存每个idle状态的一些统计数据

(9)用于sysfs相关的kobj

(10)有些平台的cpu不能独立执行上下电等操作,从而使相关cpu的idle状态相互依赖。因此在执行cpu idle操作时,需要考虑这种依赖关系。cpu idle coupled特性即用于解决该问题,其中coupled_cpus就是表示与本cpu有依赖关系的cpu集合。由于这种特性与cpu idle的主功能没有关系,因此本文将不对该特性做详细介绍

2.2 cpu idle驱动
  cpu idle驱动用于管理实际的cpu idle状态,它主要包含一个该驱动支持的cpu idle状态数组,以及一些其它配置参数。以下为其定义:

struct cpuidle_driver {
	const char		*name;
	struct module 		*owner;
	unsigned int      bctimer:1;1struct cpuidle_state	states[CPUIDLE_STATE_MAX];2int			state_count;3int			safe_state_index;4struct cpumask    *cpumask;5const char		*governor;6};

(1)当cpu idle状态设置了CPUIDLE_FLAG_TIMER_STOP标志,就会在进入idle时停掉其local timer。从而导致其自身无法唤醒该cpu上的定时器,因此内核实现了broadcast timer机制。

它会选择一个未睡眠cpu的timer作为broadcast timer,当其它cpu进入idle且关闭local timer之后,其对应的下一次到期定时器将会由broadcast timer代理执行。当定时器到期后,broadcast定时器会通过ipi方式将该事件通知到idle cpu,从而唤醒该cpu。

本参数就是cpu idle框架使用,用于指示在cpu idle驱动注册和注销时,是否需要设置一个broadcast timer

(2)cpu idle状态数组,用于保存该驱动支持的所有cpu idle状态相关信息,这些状态按照功耗从大道小的顺序进行排序

(3)该驱动支持的cpu idle状态数量

(4)该参数与coupled状态相关

(5)该驱动支持的cpu集合掩码

(6)用于指定该驱动希望使用的cpu idle governor。若不指定该值,则通过系统中已注册governor的rating,选择其中一个分值最高的来使用

2.3 cpu idle状态
  由前面讨论可知,cpu idle的目标是在cpu进入空闲状态时,根据不同睡眠状态下的功耗和延迟参数,确定cpu应该要进入哪一种睡眠模式。

为此内核为每一种睡眠模式定义了一个状态结构体,用于表示其在对应状态下的相关参数,以下为其定义:

struct cpuidle_state {
	char		name[CPUIDLE_NAME_LEN];
	char		desc[CPUIDLE_DESC_LEN];

	s64		exit_latency_ns;1)
	s64		target_residency_ns;2unsigned int	flags;3unsigned int	exit_latency;               
	int		power_usage;4unsigned int	target_residency;

	int (*enter)	(struct cpuidle_device *dev,
			struct cpuidle_driver *drv,
			int index);5int (*enter_dead) (struct cpuidle_device *dev, int index);
	int (*enter_s2idle)(struct cpuidle_device *dev,
			    struct cpuidle_driver *drv,
			    int index);
};

(1)exit_latency_ns:表示若使用这种idle状态,则cpu被唤醒退出idle时需要消耗的时间。该值以ns为单位,相应地exit_latency与其含义相同,但以ms为单位。这个值用于衡量系统对退出延迟的容忍度

(2)target_residency_ns:由于每种idle状态的切换需要消耗能量,睡眠状态越深则其切换所消耗的能量也越多。因此,若睡眠时间较短,却使用较深的状态是得不偿失的。为此只有当目标睡眠时间超过一个特定值之后,才会使用这种睡眠状态,这个参数即用于表示该值。而governor的一个关键功能就是估计idle可能的睡眠时间

(3)idle相关的标志

(4)该idle状态下的cpu功耗

(5)cpu进入idle状态的回调函数

2.4 cpu idle governor
cpu idle governor用于实现cpu idle睡眠状态的选择策略,以下为其结构体定义:

struct cpuidle_governor {
char			name[CPUIDLE_NAME_LEN];1struct list_head 	governor_list;2unsigned int		rating;3int (*enable)	(struct cpuidle_driver *drv,
				struct cpuidle_device *dev);4void (*disable)(struct cpuidle_driver *drv,
				struct cpuidle_device *dev);5int (*select)	(struct cpuidle_driver *drv,
				struct cpuidle_device *dev,
				bool *stop_tick);6void (*reflect)	(struct cpuidle_device *dev, int index);7}

(1)governor的名字

(2)内核将所有已注册的governor加入到一个链表中,该参数为其链表节点

(3)该governor的评分

(4)使能cpu idle设备的governor

(5)通知cpu idle设备的governor

(6)该函数是governor的主函数,用于选择idle的state

(7)该函数用于更新统计数据

2.5 cpu idle软件架构
  cpuidle软件框架与内核其它模块类似,也包括核心层,设备层,通用驱动层与架构相关的驱动层,此外它还包括用于选择cpuidle模式的governor模块。最后cpu的idle状态是由内核调度系统触发的,因此最终的服务对象为调度子系统。综上所述,其相应的软件架构图如下:
  在这里插入图片描述
3 cpu idle初始化流程
  cpu idle初始化包括governor注册、驱动注册和设备注册三部分,其中驱动和设备注册流程由架构相关的初始化流程执行。由于arm64的通用cpu idle代码框架采用psci方式(driver/cpuidle/cpuidle-psic.c)实现。因此,在后面介绍cpu idle设备和驱动注册流程时将以这种方式为例

3.1 governor注册
  cpu idle governor在cpu idle驱动和设备之前注册,内核使用一个链表维护系统中所有已注册的governor。且在每个新governor被注册时都会将其rating值与已注册governor的rating值比较,并将rating值最高的governor最为当前governor。其注册流程如下:
  在这里插入图片描述

当前内核一共支持ladder、menu、teo和haltpoll四种governor,它们都通过调用cpuidle_register_governor函数将自身注册到系统中。该函数的定义如下:

int cpuidle_register_governor(struct cpuidle_governor *gov)
{if (cpuidle_find_governor(gov->name) == NULL) {
		ret = 0;
		list_add_tail(&gov->governor_list, &cpuidle_governors);1if (!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);2}}

(1)将该governor加入全局链表cpuidle_governors中

(2)将该governor的rating值与current governor的rating值比较,若其分值更高则将current governor切换为新governor

3.2 psci方式的idle框架初始化
  arm64架构的通用cpu idle初始化流程主要包括解析设备树的idle配置参数,注册cpu idle驱动和设备等,其调用流程如下:

在这里插入图片描述
3.2.1 设备树解析
设备树可以通过以下两种方式描述cpu idle的states:
(1)通过层次化的power-domains和domain-idle-states属性描述

(2)通过cpu-idle-states属性描述

下面为一个通过cpu-idle-states属性描述的例子:

cpu0: cpu@0 {
			compatible = "arm,cortex-a53";
			device_type = "cpu";
			reg = <0x0 0x0>;
			enable-method = "psci";
			next-level-cache = <&A53_L2>;
			cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;};
		...
cpu4: cpu@100 {
			compatible = "arm,cortex-a73";
			device_type = "cpu";
			reg = <0x0 0x100>;
			enable-method = "psci";
			next-level-cache = <&A73_L2>;
			cpu-idle-states = <&CPU_SLEEP_1 &CLUSTER_SLEEP_1>;}
		…
	idle-states {
			entry-method = "psci";

			CPU_SLEEP_0: cpu-sleep-0 {
				compatible = "arm,idle-state";               
				local-timer-stop;1)
				arm,psci-suspend-param = <0x0010000>;2)
				entry-latency-us = <400>;3)
				exit-latency-us = <650>;4)
				min-residency-us = <1500>;5};
			CLUSTER_SLEEP_0: cluster-sleep-0 {
				compatible = "arm,idle-state";
				local-timer-stop;
				arm,psci-suspend-param = <0x1010000>;
				entry-latency-us = <500>;
				exit-latency-us = <1600>;
				min-residency-us = <3500>;
			};}

其中idle-states节点中各属性的含义如下:
(1)用于指定cpu在进入idle状态时是否需要停止其local timer

(2)用于指示该状态state对应的参数,该值会通过psci命令传给bl31,并由bl31处理

(3)cpu进入该idle state时的延迟时间

(4)cpu从该idle state中唤醒的延迟时间

(5)由于进入不同idle state时的代价不同,因此cpu在进入idle之前,会估计其预期驻留在idle状态的时间,只有当其预期驻留时间超过min-residency-us时,使用该state才有价值

以上这些参数由dt_init_idle_driver init_state_node函数解析,其代码如下:

static int init_state_node(struct cpuidle_state *idle_state,
			   const struct of_device_id *match_id,
			   struct device_node *state_node)
{
…
	idle_state->enter = match_id->data;1)
	idle_state->enter_s2idle = match_id->data;

	err = of_property_read_u32(state_node, "wakeup-latency-us",
				   &idle_state->exit_latency);2if (err) {
		u32 entry_latency, exit_latency;
		err = of_property_read_u32(state_node, "entry-latency-us",
					   &entry_latency);                         
		…
		err = of_property_read_u32(state_node, "exit-latency-us",
					   &exit_latency);                          
		…
		idle_state->exit_latency = entry_latency + exit_latency;3}

	err = of_property_read_u32(state_node, "min-residency-us",
				   &idle_state->target_residency);4)
	…
	err = of_property_read_string(state_node, "idle-state-name", &desc);5if (err)
		desc = state_node->name;

	idle_state->flags = 0;
	if (of_property_read_bool(state_node, "local-timer-stop"))6)
		idle_state->flags |= CPUIDLE_FLAG_TIMER_STOP;}

1)该参数用于设置进入该idle状态的回调函数。它通过设备match结构体的data成员指定,如对于arm64架构通用idle初始化流程,其定义如下(driver/cpuidle/cpuidle-psci.c):

static const struct of_device_id psci_idle_state_match[] = {
	{ .compatible = "arm,idle-state",
	  .data = psci_enter_idle_state },
	{ },
}

(2)通过wakeup-latency-us属性获取idle状态的退出延迟时间

(3)若wakeup-latency-us属性未指定,则通过entry-latency-us和exit-latency-us功能共同计算退出延迟

(4)根据min-residency-us属性,获取该状态的最小idle驻留时间

(5)获取该状态的名字

(6)根据local-timer-stop属性,确定cpu进入idle时是否需要停止local timer

3.2.2 cpu idle驱动注册
cpu idle驱动注册流程比较简单,它主要包含以下三部分内容
(1)idle state相关参数设置、以及可能的broadcast timer

(2)若设置了local-timer-stop属性,则为每个cpu设置相应的broadcast timer

(3)若为该driver指定了governor,则切换current governor

以下为其代码实现:

int cpuidle_register_driver(struct cpuidle_driver *drv)
{
	…
	ret = __cpuidle_register_driver(drv);1)
	…
	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)2)
				cpuidle_prev_governor = NULL;
		}
		mutex_unlock(&cpuidle_lock);
	}}
1)驱动注册主流程

(2)执行cpu idle governor切换操作

其中__cpuidle_register_driver的主要实现如下:

```c
static int __cpuidle_register_driver(struct cpuidle_driver *drv)
{__cpuidle_driver_init(drv);1)
	ret = __cpuidle_set_driver(drv);2if (ret)
		return ret;

	if (drv->bctimer)
		on_each_cpu_mask(drv->cpumask, cpuidle_setup_broadcast_timer,
				 (void *)1, 1);3return 0;
}

(1)设置该idle state的一些相关参数

(2)将驱动指针保存到全局变量中

(3)若设置了local-timer-stop属性,则为每个cpu设置broadcast timer

**3.2.3 cpu idle设备注册**
  cpu idle设备注册主要包括初始化一些参数值,将该设备添加到全局设备链表中,然后为其初始化sysfs属性和使能该设备。其代码流程如下:

```c
int cpuidle_register_device(struct cpuidle_device *dev)
{
	…
	__cpuidle_device_init(dev);                  (1)

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

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

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

	cpuidle_install_idle_handler();              (5)
	…
}

(1)初始化该设备对应的统计数据等参数

(2)设置cpu idle设备统计数据的disable标志,并将该设备添加到全局设备链表中

(3)初始化该设备对应的sysfs属性

(4)使能对应的sysfs设备

(5)设置设备初始化完成标志

4 cpu idle触发流程
  cpu启动完成时,会通过cpu_startup_entry函数将其自身切换到idle线程。除此之外,当某个cpu上没有可运行线程时,也会切换idle线程。它们的切换流程分别如下:
(1)primary cpu启动时切换到idle线程流程
在这里插入图片描述
(2)secondary cpu启动时切换到idle线程流程

在这里插入图片描述
(3)cpu上没有可执行任务时切换到idle线程流程
在这里插入图片描述
  以上这几种方式最终都会执行idle线程的主函数do_idle,并最终通过该函数将cpu设置为特定的idle状态。以下为arm64架构下该函数的主要执行流程
  在这里插入图片描述
从以上流程可知,内核有两种进入cpu idle的方式:
(1)当系统未注册cpu idle模块时,架构为内核提供了一个默认的cpu idle函数。该函数的实现很简单,当cpu需要进入idle状态时,直接执行wfi指令即可

(2)当系统注册了cpu idle模块,则通过cpuidle模块的当前governor选择一个合适的idle state,然后通过call_cpuidle函数执行相应的idle操作。该函数最终会调用state对应的enter函数,以使cpu进入idle状态

对于非acpi启动方式的arm64架构的cpu,该函数会调用psci接口陷入bl31,然后由bl31执行最终的硬件操作。该流程如下:

在这里插入图片描述
 最后再看一下cpu何时会退出idle状态。我们知道内核调度器为了平衡cpu之间的负载,当新进程被创建或睡眠进程被唤醒时,需要为其重新选择cpu,从而可向空闲cpu分配任务。此外,在负载均衡流程中负载轻的cpu也会从其它负载重的cpu上拉取任务,它也可使cpu从idle状态转换为运行状态

5 cpu idle governor
  从cpu idle流程中可看出,governor用于确定cpu应该采用哪一种idle state。它的主要依据为cpu预期空闲时间和idle退出延迟等。在介绍具体governer之前,我们先看一下这些参数的含义:
(1)退出延迟(exit_latency)
  该参数用于表示cpu进入某个idle state后,退出该state所需要的延迟时间。由于功耗越低,延迟时间越长,因此为了尽量降低功耗, governor应该在满足系统容忍度前提下,选择功耗最低的state。

(2)idle目标驻留时间(target_residency)
  由于cpu进入和退出idle状态是需要消耗能量的,因此若其在idle状态驻留时间太短,并不能达到降低功耗的目的。为此,每种idle状态都定义了一个最小驻留时间,为governor提供是否需要进入该state的参考

(3)延迟容忍度(latency_req)
  该参数用于提供系统的延迟容忍度,governor可用该值判断给定idle state是否能满足系统的延迟要求

(4)预期驻留时间
  由于每种idle state都提供了一个idle目标驻留时间,而实际上cpu并不知道其何时会被唤醒。因此需要采用一些预测算法,来预测其可能的idle驻留时间,该时间被称为预期驻留时间

(5)下一个定时器到期时间
  由于cpu上的定时器到期表明其有任务需要处理,因此最近一次到期任务的时间可被用于预测其预期驻留时间

为了满足不同的需求,内核当前一共实现了haltpoll、ladder、teo和menu四种governor。下面我们将重点介绍haltpoll governor和ladder governor的实现原理,大家若对其它两种governor感兴趣,可以自行分析一下

5.1 haltpoll governor
  它是用于优化虚拟机性能的一种cpu idle governor。其原理为当vcpu进入idle时,通过guest端执行poll操作,以避免使其陷入host中。它的优点是减少了vm切换和通过ipi唤醒vcpu的成本,但它也造成在guest睡眠时,host无法复用该vcpu对应的物理cpu,从而降低系统吞吐量的问题。

它一共定义了两种idle state,其中state[1]使用默认的idle实现,其相应的定义如下(drivers/cpuidle/cpuidle-haltpoll.c):

static struct cpuidle_driver haltpoll_driver = {
	.name = "haltpoll",
	.governor = "haltpoll",
	.states = {
		{ /* entry 0 is for polling */ },
		{
			.enter			= default_enter_idle,1.exit_latency		= 1,
			.target_residency	= 1,
			.power_usage		= -1,
			.name			= "haltpoll idle",
			.desc			= "default architecture idle",
		},
	},
	.safe_state_index = 0,
	.state_count = 2,
}

static int default_enter_idle(struct cpuidle_device *dev,
			      struct cpuidle_driver *drv, int index)
{
	if (current_clr_polling_and_test()) {
		local_irq_enable();
		return index;
	}
	default_idle();2return index;
}

(1)state[1]通过default_enter_idle回调使用默认的idle处理函数

(2)调用默认idle处理函数

而state[0]通过cpuidle_poll_state_init函数定义,其代码如下:

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

该state的idle处理函数为poll_idle,其原理设置一个poll的到期时间,然后执行轮询操作,直到时间到期后退出。其代码如下(drivers/cpuidle/poll_state.c):

static int __cpuidle poll_idle(struct cpuidle_device *dev,
			       struct cpuidle_driver *drv, int index)
{
	u64 time_start = local_clock();if (!current_set_polling_and_test()) {
		unsigned int loop_count = 0;
		u64 limit;

		limit = cpuidle_poll_time(drv, dev);1while (!need_resched()) {
			cpu_relax();
			if (loop_count++ < POLL_IDLE_RELAX_COUNT)
				continue;

			loop_count = 0;
			if (local_clock() - time_start > limit) {2)
				dev->poll_time_limit = true;
				break;
			}
		}
	}}

(1)获取轮询到期时间

(2)判断轮询时间是否到期,若到期则退出

相应地其governor的state选择函数也很简单,主要是根据一些条件判断cpu是使用轮询方式还是默认方式进入idle状态,以下为其代码实现:

static int haltpoll_select(struct cpuidle_driver *drv,
			   struct cpuidle_device *dev,
			   bool *stop_tick)
{
	s64 latency_req = cpuidle_governor_latency_req(dev->cpu);

	if (!drv->state_count || latency_req == 0) {
		*stop_tick = false;
		return 0;
	}
	if (dev->poll_limit_ns == 0)
		return 1;

	if (dev->last_state_idx == 0) {
		if (dev->poll_time_limit == true)
			return 1;
		return 0;
	}
	return 0;
}

5.2 ladder governor
  该governor通过cpu前一次idle状态的驻留时间是否超过该state延迟时间一个特定的值(promotion_time_ns),以及下一个state的延迟时间是否超过系统延迟容忍度,来确定是否需要提升idle state。由于该governor每次只能提升一个state,因此其state提升方式就像梯子一样逐级往上,这也是它的名字由来。以下为其state选择函数:

static int ladder_select_state(struct cpuidle_driver *drv,
			       struct cpuidle_device *dev, bool *dummy)
{if (unlikely(latency_req == 0)) {
		ladder_do_selection(dev, ldev, last_idx, 0);
		return 0;
	}

	last_state = &ldev->states[last_idx];

	last_residency = dev->last_residency_ns - drv->states[last_idx].exit_latency_ns;

	if (last_idx < drv->state_count - 1 &&
	    !dev->states_usage[last_idx + 1].disable &&
	    last_residency > last_state->threshold.promotion_time_ns &&
	    drv->states[last_idx + 1].exit_latency_ns <= latency_req) {1)
		last_state->stats.promotion_count++;
		last_state->stats.demotion_count = 0;
		if (last_state->stats.promotion_count >= last_state->threshold.promotion_count) {
			ladder_do_selection(dev, ldev, last_idx, last_idx + 1);2return last_idx + 1;
		}
	}

	if (last_idx > first_idx &&
	    (dev->states_usage[last_idx].disable ||
	    drv->states[last_idx].exit_latency_ns > latency_req)) {3int i;

		for (i = last_idx - 1; i > first_idx; i--) {
			if (drv->states[i].exit_latency_ns <= latency_req)
				break;
		}
		ladder_do_selection(dev, ldev, last_idx, i);
		return i;
	}

	if (last_idx > first_idx &&
	    last_residency < last_state->threshold.demotion_time_ns) {4)
		last_state->stats.demotion_count++;
		last_state->stats.promotion_count = 0;
		if (last_state->stats.demotion_count >= last_state->threshold.demotion_count) {
			ladder_do_selection(dev, ldev, last_idx, last_idx - 1);5return last_idx - 1;
		}
	}

	return last_idx;
}

(1)其下一个state的延迟小于系统延迟容忍度,且上一次idle驻留时间减去退出延迟大于promotion_time_ns,则表明其具有提升state的空间

(2)只有当以上条件满足promotion_count指定的次数(当前该值被定义为4)后,才会实际提升idle state

(3)若最后一次进入idle时对应state的延迟时间大于延迟容忍度,则需要降低state id。由于延迟容忍度是系统硬指标,因此其state id不能采用ladder方式下降,而应该直接降低到可满足延迟容忍度的最大 state id

(4)上一次idle驻留时间减去退出延迟小于demotion_time_ns,则表明其具有下降state的空间

(5)只有当以上条件满足demotion_count指定的次数(当前该值被定义为1)后,才会实际下降idle state

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐