一文搞懂linux cpu idle

​1. 介绍

Linux系统初始化时会为每个cpu创建一个idle线程,当没有其他进程需要运行的时候,便运行idle线程。

对于不同的功耗及恢复时间的要求,可以根据芯片硬件支持的情况定义多种idle状态,这些状态按功耗从低到高(对应着恢复时间从少到多)排列,利用linux提供的cpuidle框架,用户选用不同的idle策略。这么做的目的就是尽可能在不影响性能的前提下,减少功耗。

在ARM64架构中,至少会提供一个wfi的idle状态,有些芯片可能还会提供core下电的idle状态。当CPU idle时,根据预测的idle时间、功耗受益大小、恢复的时间长短,选用一个idle状态,比如进入wfi,关掉CPU的arch timer以便降低功耗,当有中断触发时,CPU又会恢复回来。

2. 框架

cpuidle框图

1)scheduler模块:位于kernel\sched\idle.c中,负责实现idle状态的选择、idle的进入等等。

2)cpuidle core模块:cpuidle core抽象出cpuidle device、cpuidle driver、cpuidle governor三个实体。

  • 以函数调用的形式,向上层sched模块提供接口。
  • 以sysfs的形式,向用户空间提供接口。
  • 向下层的cpuidle drivers模块,提供统一的driver注册和管理接口。
  • 向下层的cpuidle governors模块,提供统一的governor注册和管理接口。

3)cpuidle governors模块:提供多种idle的策略,比如menu/ladder/teo/haltpoll。位于governors/目录下。

4)cpuidle drivers模块:负责idle机制的实现,即:如何进入idle状态,什么条件下会退出。

3. 数据结构

cpuidle core抽象出了三个数据结构:

  • cpuidle device:用于描述CPU核的cpuidle设备。
  • cpuidle driver:用于描述CPU核的cpuidle驱动。
  • cpuidle governor:主要根据cpuidle的device和driver状态来选择策略。

cpuidle数据结构

4. 初始化及工作流程

4.1 cpuidle初始化流程

以cpuidle-pcsi.c为例,整个cpuidle注册流程如下图:

cpuidle注册流程

cpuidle初始化包括governor注册、驱动注册和设备注册三部分,其中驱动和设备注册流程由架构相关的初始化流程执行。

1) governor注册

cpuidle governor在cpuidle驱动和设备之前注册,内核使用一个链表维护系统中所有已注册的governor。且在每个新governor被注册时都会将其rating值与已注册governor的rating值比较,并将rating值最高的governor做为当前governor。

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

  • Haltpoll governor:它是用于优化虚拟机性能的一种cpuidle governor。其原理为当vcpu进入idle时,通过guest端执行poll操作,以避免使其陷入host中。它的优点是减少了vm切换和通过ipi唤醒vcpu的成本,但它也造成在guest睡眠时,host无法复用该vcpu对应的物理cpu,从而降低系统吞吐量的问题。
  • Ladder governor:该governor通过cpu前一次idle状态的驻留时间是否超过该state延迟时间一个特定的值(promotion_time_ns),以及下一个state的延迟时间是否超过系统延迟容忍度,来确定是否需要提升idle state。由于该governor每次只能提升一个state,因此其state提升方式就像梯子一样逐级往上,这也是它的名字由来。它往往用于periodic timer tick system。
  • Menu governor:直接选择可能满足需求的最深休眠态,就好像你拿着菜单(menu)选菜一样。如果深度的idle state更好,那么就会直接进入到深度的idle state。
  • Teo governor:采用的策略跟menu governor一样,都是预测接下来会有多长时间能待在idle状态,然后据此选择合适的idle mode。不过它跟menu governor考虑多方因素的策略是不同的。teo的理念是,多数系统上CPU唤醒最频繁的唤醒源都是timer events,而不是设备中断(device interrupts)。timer中断的数量要比其他中断高几个数量级。所以只要依据timer event就可以做好预测工作了。

2) cpuidle driver注册

cpuidle驱动注册流程比较简单,它主要包含以下三部分内容

  • idle state相关参数设置、以及可能的broadcast timer
  • 若设置了local-timer-stop属性,则为每个cpu设置相应的broadcast timer
  • 若为该driver指定了governor,则切换current governor

3) cpuidle device注册

cpuidle设备注册主要包括初始化一些参数值,将该设备添加到全局设备链表中,然后为其初始化sysfs属性和使能该设备。

注册之后,cpuidle设备、cpuidle驱动及governor之间建立起了连接,最终系统经由cpuidle framework,通过接口来调用下层的接口,进而完成具体的硬件操作。

4.2 cpuidle触发流程

Idle task通过cpu_startup_entry为入口,调用到cpuidle_framework,流程如下图:

cpuidle触发流程

cpu启动完成时,会通过cpu_startup_entry函数将其自身切换到idle线程。除此之外,当某个cpu上没有可运行线程时,也会切换idle线程(上流程没画出,后面梳理进程调度的时候再细讲)。切换idle线程后,最终都会执行idle线程的主函数do_idle,并最终通过该函数将cpu设置为特定的idle state。

其中governor中的select、reflect函数是cpuidle的核心功能,决定了cpuidle状态的选择策略。

5. 策略核心函数分析

5.1 select函数

governor的select,以menu_select为例:

menu_select函数

1)预期idle的predict时间的计算:

menu governor会将下一个tick到来的时间(next_timer_us)作为一个基础的predicted_us,并在这个基础上调整。

首先,因为predicted_us并不总是与next_timer_us直接相等,在等待下一个tick的过程很有可能被其他事件所唤醒,所以需要引入校正因子(correction factor)校正predicted_us。此校正因子从对应的bucket索引中选取。

menu governor使用了一个12组校正因子来预测idle时间,校正因子的值基于过去predicted_us和next_timer_us的比率,并且采用动态平均算法。

另外对于不同的next_timers_us,校正因子的影响程度是不一样的。对于不同的iowait场景,系统对校正因子也有着不同的敏感程度 。

随后尝试通过最近的8个过去的停留时间来查找重复间隔,如果他们的标准差小于一定阈值,则将这8个时间的平均值作为predicted_us。

最后取以上两个流程中的最小值。

2)系统延迟容忍时间的计算

对于系统容忍度,menu governor使用性能乘数(performance multiplier)、预计停留时间(predicted)和系统延迟需求(latency requirement)来找出最大退出延迟。

系统延迟容忍时间作为第一个系统延迟容忍度。另外一个系统iowait容忍度计算如下:predicted_us / (1 +10 * iowaiters)。iowaiters指当前cpu上iowait的进程数。

最后取前面两个系统容忍度中最小值,作为最小的系统容忍度。

3)idle state的选取

最后根据前面计算出来的两个因素来选取具体的idle state,将计算出的predicted_us与所有idle状态的停留时间进行比较,选择特定idle state的条件是相应的停留时间应小于predicted_us。

另外,将状态的exit_latency与系统的延迟要求进行比较。基于两个等待时间因素,选择适当的idle state。

5.2 reflect函数

menu_reflect函数

在cpu退出idle状态后,menu governor会将将上一次进入idle状态的数据更新menu driver中的相关数据,作为下一次select的参数。

5.3 update函数

下一次进入idle state选择流程时,会先触发更新需求,即进入到menu_update()中。

menu_update函数

1)idle的存留时间计算

在更新信息时,会尝试算出进入idle状态到被唤醒经历了多长时间。

如果cpu被tick唤醒,而且上次记录的next_timer_us大于了一个tick的时间,那么governor就假定cpu已经空闲了很长时间,则measured_us = 9 * MAX_INTERESTING / 10(INTERESTING=50000)

如果cpu退出了轮询状态,会导致选择该状态的空闲持续时间不准确,故将next_timer_us作为measured_us。

除此之外,measured_us将使用驱动中记录的上次idle状态中停留时间。

算出来之后再减去退出延迟,然后与next_timer_us取最小值,便得出了最终的measured_us。

2)校正因子的计算

接下来是计算下一次选择校正因子(correction factor)的值,将上一次的校正因子先衰减一次,然后加上一个predicted_us和next_timer_us的比值new_factor += RESOLUTION * measured_us / data->next_timer_us;(RESOLUTION=1024)

最后就可以将idle存留时间及校正因子更新到governor的驱动中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值