何时休眠

当CPU没事可做的时候,软件层面采用的做法通常是:执行一个idle任务,这个idle任务是系统中优先级最低的,里面一般就是一个无限循环。但它还是会消耗能量,这实在有点不划算,对于移动设备更是浪费宝贵的电池资源,但是idle任务又必须执行,系统不能处在一个没有任何任务运行的状态。

功耗模式 – 睡眠深度

为此,CPU的设计者在硬件层面提供了不同的运行模式,以Intel的芯片为例(同时也是ACPI 的spec定义),包括了P-state和C-state。

在C-state的划分中,C0为正常工作模式,其他的”C1 ~ Cn”为休眠模式,这样CPU在无事可做时,就不用靠执行一些useless的指令来维持运行(软件idle),idle时进入休眠模式即可(称为”cpuidle”,或硬件/physical idle,对应的内核配置选项是”CONFIG_CPU_IDLE”)。

有的休眠态是关闭部分时钟/电源(浅睡),有的是全部关闭(深睡),以供软件根据不同的场景选择。

休眠模式是存在代价的:进入和退出需要花费时间,退出恢复到正常状态的latency会影响对任务的响应,而且,进入和退出的过程本身也会损失一定的功耗(实际的”Entry”和”Exit”的功耗甚至可能短暂超过正常运行时的功耗)。

linux梯子_梯子梯子_梯子link

“n”值越大,睡眠程度越深,功耗节省越多,但恢复时间也越长。如果睡眠时间比较短,功耗没节省多少,时延又长,就很不合算。

基本概念

在Linux中,描述C-state的是”cpuidle_state”结构体。一个SMP系统有多个CPU,一个CPU有多种休眠态,这形成了一个二维的矩阵,”cpuidle_state”就是此矩阵的纵向元素。

struct cpuidle_state {
// 在sysfs中可见
char name[16];
char desc[32];

int power_usage; /* 以mW计 */
unsigned int exit_latency; /* 以us计 */
unsigned int target_residency; /* 以us计 */

int (*enter) (...);

};

linux梯子_梯子梯子_梯子link

梯子link_linux梯子_梯子梯子

再来从横向的角度看一下。将一个CPU中和实现idle功能相关的部分抽离出来,就形成了"cpuidle_device",可理解为一种虚拟设备吧。

DEFINE_PER_CPU(struct cpuidle_device, cpuidle_dev);


struct cpuidle_device {

unsigned int enabled:1;
unsigned int use_deepest_state:1;

unsigned int cpu; /* 对应的CPU编号 */

struct cpuidle_state_usage states_usage[10];

}

struct cpuidle_state_usage {
unsigned long long usage;

unsigned long long time;

unsigned long long above;
unsigned long long below;

...

}

这些信息可通过"/sys/devices/system/cpu/cpuidle/"查看。

策略制定

选择进入哪个休眠态,是cpuidle子系统的重点,负责这件事的执行体被称为"governor",由"cpuidle_governor"结构体表示(定义在"/include/linux/cpuidle.h"):

struct cpuidle_governor {
int (*enable)(struct cpuidle_driver *drv, struct cpuidle_device *dev);
int (*select)(struct cpuidle_driver *drv, struct cpuidle_device *dev, bool *stop_tick);
void (*reflect)(struct cpuidle_device *dev, int index);

...

};

自信与保守

具体的做法可以是:先试探性地进入比较"shallow"的C-state,如果发现休眠的时间足够长,才进入更deeper一些的(promotion),就像爬梯子(ladder)一样,所以被称为"ladder governor"。

相对来说,promotion时会更谨慎一些,需要实际休眠时间连续4次超过预估休眠时间,才跃升一级,而只要实际休眠时间低于预估休眠时间1次,demotion就会发生。因为睡浅了最多就是功耗节省少一些,睡深了会影响任务的响应时限,轻重缓急有所不同。

linux梯子_梯子link_梯子梯子

还有一种策略是直接选择可能满足需求的最深休眠态,就好像你拿着菜单(menu)选菜一样,看中后直接点你认为可能最好吃的,因此叫做"menu governor"(包括由此衍生的TEO governor,参考what are ladder governors and menu governors)。

周期性的tick触发会唤醒idle状态的CPU,导致一次idle的时间不能超过一个tick的长度,目前Linux默认会使用dyntick-idle模式,进入idle状态后关闭tick,在这种模式下,干扰更小,确定性更强,使用menu governor往往能取得更好的效果。

可能正因为此,memu策略也是目前Linux默认采用的cpuidle选择算法(实现定义在"drivers/cpuidle/governors/menu.c"),两种策略可通过"cpuidle.governor="的命令行参数在启动时切换。

梯子梯子_linux梯子_梯子link

如何增加胜率

如果governor预测接下来idle的时间会比较长,那么它将选择相对比较“深”的休眠态。假设实际idle状态持续的时间也确实比较长,那当然皆大欢喜(win)。而如果实际是一个short idle,由于进入和退出"dyntick-idle"模式本身也存在开销,那可能就得不偿失,还没回本就结束了(loss)。

如果governor预测是short idle,那它将选择相对比较“浅”的休眠态。假设实际是long idle,那可以节省的功耗就大打折扣。实际是short idle呢,停止和重启tick的开销得不到弥补,横竖都是“输”。

梯子梯子_梯子link_linux梯子

Intel开源技术中心的工程师Rafael J. Wysocki在这个机制的基础上做了一些调整,由governor已经做出判断后,再决定是否停止tick(对应函数参数"stop_tick")。

如果governor判断是long idle,还是会停止tick,所以结果和原方案一样(第一行)。但如果判断是short idle(第二行),就不停止tick。当实际也为short idle时,由于不需要停止和重启tick,减少了不必要的开销;而实际为long idle时,可以被这个没有停止的tick唤醒,重新选择更“深”一些的休眠态,及时止损。

梯子梯子_linux梯子_梯子link

理论上,这个新的方案可以增加governor的胜率。而后,Wysocki通过实验进一步验证了新方案在降低波动性和整体功耗上的有效性。该方案已被4.17及之后的内核版本所采用。

绿色为原版本,红色为新版本

tick这种定时器的timeout属于“完全可预测“的唤醒事件,还有一类是”部分可预测“的I/O事件,为了将这类因素的影响考虑进去,采用将等待I/O的线程数量作为矫正因子,加入对预估休眠时间的计算中,随"io_wait"数量的增多,预估时间相应地减少。

linux梯子_梯子link_梯子梯子

本网站每日更新互联网创业教程,一年会员只需98,全站资源免费下载点击查看会员权益

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注