13

Menu Governor

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI3NzA5MzUxNA%3D%3D&%3Bmid=2664608169&%3Bidx=1&%3Bsn=6eeda0112f0b1a4e2e6b24837763ebed
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在现代操作系统中,往往很多时候并不需要去执行cpu密集型的任务,而每当这个时候,如果去持续的执行循环去等待新的任务发过来,那需要消耗巨大的能量。所以设计人员设计出一种空闲状态以此CPU进入低功耗模式。在Linux系统中,系统中的程序在CPU上运行,当执行完成后便让出CPU,而这个时候CPU无需执行任何程序,并且也没有任何中断、异常信号发过来,CPU便会进入一种空闲的状态,一般称这种状态为cpu idle状态。

一、 CPU idle 框架概述

CPU idle框架主要分为cpu idle governor,cpu idle driver,cpu idle core。

cpu idle governor,cpu idle driver,cpu idle core三者的关系如下图所示:

V73memF.jpg!web

CPU调度器发现没有任务在运行时,会切换到idle进程,通过cpuidle_idle_call接口调到cpuidle framework中,cpuidle framework内部会选择适当的策略在决定进入哪种状态,然后回调到driver端实现。

当CPU进入idle状态后,CPU将停止时钟并且部分硬件将会停止使用,以此来减少CPU所消耗的能量。但是在这种情况下,该CPU的性能会受到很大限制。控制好CPU如何进入/退出CPU idle深度(即C state)是CPU idle子系统中一个关键点。

C state根据不同的深度会存在不同的退出延迟和功耗,如下图所示

y6F7nuy.png!web

本文主要是对CPU idle governor的一个粗略介绍。本文所有代码选自Linux Kernel5.0.7。

二、 CPU idle 进入/退出流程简述

Linux系统启动的时候,会在每一个CPU上创建对应的cpu idle线程。系统初始化完成后,将init线程转化为idle线程。在init/main.c中start_cpu()函数最后会调用 arch_call_rest_init()将init进程转化为idle进程,最终进入到cpu_startup_entry()中进入到无限的idle loop中。

YBfEfmV.png!web

在do_idle()中,代码会不断地轮询,判断当前系统是否需要调度,如果系统当前不需要调度,则进入到idle状态。

do_idle()->cpuidle_idle_call()->cpuidle_select()

进入cpuidle流程后,会根据系统中对应governor策略进而选择不同的idle state。

NFruauv.png!web

系统在选择完idle state后,还会调用cpuidle_reflect()将选取过程中一些信息和结果保留下来,以备下一次选取使用。

在进入idle状态后,CPU会调用WFI指令(wait for interrupt),直到有中断到来,系统就会退出idle状态。

三、CPU idle governor

1.综述

在实际CPU运行环境中,不同的CPU他们对idle状态的需求和进入/退出方法会存在差别,而这其中功耗和退出延迟成为了在idle调度过程一组不可调和的矛盾,如何保证在满足性能需求的前提下尽可能的节省功耗成了CPU idle子系统的一个重要组成部分。CPU idle governor在整个CPU idle子系统中负责提供如何使用CPU idle的策略。

内核中提供了两种策略:Menu和Ladder。选择哪种调度器,取决于内核的配置,其中关键点是系统调度的tick是否可以被空闲循环停止。在系统初始化过程中,menu和ladder分别通过cpuidle_register_governor()注册到系统中。

ladder governor会首先进入最浅的idle state,然后如果待的时间足够长,则会进入到更深一级的idle state,以此类推,直到到达最深的idle state。当被唤醒时,会尽可能快地重新启动CPU;等到下次空闲,则又会从idle state1开始进入。它往往用于periodic timer tick system。

而在tickless system中,ladder可能会存在没有机会进入到更深一级的idle状态中,引起功耗损失,所以这个时候往往会使用menu调度器。menu调度器则不一定遵守由浅入深的规则,如果深度的idle state更好,那么就会直接进入到深度的idle state。

由于主流系统中常采用tickless system,本文重点介绍menu governor。

对于menu调度器,存在两种决定idle深度(下称C state)的因素

(1)能量平衡点(停留时间)

在进入/退出C state时,系统会消耗一定的能量,所以频繁进出C state不是一件能带来收益的事情,调度器在考虑进入C state的时候,会考虑进入该C state的预计持续时间。

(2)性能影响(系统延迟容忍度)

对于C state来说,退出C state会存在巨大的延迟,这会极大影响性能。因此对于调度器来说,系统越忙,C state就越不能被接受,系统的工作负载也被纳入决策因素中。

而以上两个因素,menu governor的职责具体到实际情况中,便转换为了两个任务:1.预测C state的停留时间。2.计算系统延迟容忍度

在struct cpuidle_state中使用target_residency来记录该C state下的停留时间阈值。在选择过程中,会计算出预计停留时间(predicted_us)和备选的C state中的target_residency进行比较,选取其中满足停留时间大于target_residency的C state。

选取时,先通过pm qos计算出系统此时的系统容忍度(latency_req),接着在所有exit_latency小于latency_req的C state中选择power_usage最小的那个state。

2.核心结构体

C state的结构体如下:

r2UFreI.png!web

  • name:该state 的名称

  • desc:该state的简介

  • exit_latency:表示退出该state的延迟,单位us

  • power_usage:表示该state下的功耗

  • target_residency:期望停留时间,单位us

  • enter:进入该state的回调函数

menu governor的结构体如下:

qQFbUbQ.png!web

  • last_state_idx:记录上一次进入的idle深度

  • need_update:在系统每次从C state返回时,会调用reflect接口,用于考虑这次state切换的结果,menu governor的reflect接口会将need_update设置为1,在下次进入select时对idle信息进行更新

  • tick_wakeup:记录上次退出C状态是否是被tick唤醒

  • next_timer_us:记录距离下一个tick到来的时间

  • bucket:记录在当前的校正因子的位置

  • correction factor:保存校正因子的数组

  • intervals、interval_ptr:计算标准差时所采用的停留时间

3.核心函数

menu governor的menu_select()的核心部分如下:

mEfI3qR.png!web

MJJrmiz.jpg!web

在计算predict的过程中,menu governor会将下一个tick到来的时间点距离此刻的时间(next_timer_us)作为一个基础的predicted_us,并在这个基础上调整。

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

menu governor使用了一组12组校正因子来预测空闲时间,校正因子的值基于过去predicted_us和next_timer_us的比率,并且采用动态平均算法。另外对于不同的next_timers_us,校正因子的影响程度是不一样的;对于不同的io wait场景,系统对校正因子也有着不同的敏感程度 。

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

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

对于系统容忍度,menu governor使用性能乘数(performance multiplier)、预计停留时间(predicted)和系统延迟需求(latency requirement)来找出最大退出延迟。系统延迟需求作为第一个系统延迟容忍度;通过公式(1)计算出另外一个系统容忍度:predicted_us / (1 +10 * iowaiters)

*iowaiters指当前cpu上iowait的任务数

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

最后根据前面计算出来的两个因素来选取具体的idle state,将计算出的predicted_us与所有idle状态的停留时间进行比较,选择特定idle状态的条件是相应的停留时间应小于predicted_us。另外,将状态的exit_latency与系统的交互性要求进行比较。基于两个等待时间因素,选择适当的空闲状态。

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

ruYV7bN.png!web

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

AZZrMbU.png!web

在更新信息时,会尝试算出进入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。

接下来是计算下一次选择校正因子(correction factor)的值

将上一次的校正因子先衰减一次,然后加上一个predicted_us和next_timer_us的比值

new_factor += RESOLUTION * measured_us / data->next_timer_us;

(RESOLUTION=1024)

最后就可以将这两个值更新到governor的驱动中。

参考资料

[1]https://www.kernel.org/doc/html/v5.0/admin-guide/pm/cpuidle.html

[2]http://www.wowotech.net/pm_subsystem/cpuidle_menu_governor.html

[3]https://www.cnblogs.com/LoyenWang/p/11379937.html

[4]S. Pattanayak and B. Thangaraju, "Linux CPU-Idle Menu Governor with Online Reinforcement Learning and Scheduler Load Balancing Statistics," 2019 IEEE International Conference on Electronics, Computing and Communication Technologies (CONECCT), Bangalore, India, 2019, pp. 1-6, doi: 10.1109/CONECCT47791.2019.9012820.

RJrieeF.gif

长按关注

内核工匠微信

Linux 内核黑科技 | 技术文章 | 精选教程

niemueI.jpg!web

赞一下你最美!

rmE7Zjy.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK