我们知道操作系统的特点是:在任何时候,CPU都有一个且只有一个任务在运行。如果没有一个任务在运行,CPU在做什么?事实证明,这种情况是如此普遍,以至于它实际上是大多数个人计算机的常态:正在休眠、等待某种条件唤醒的进程,并且将近100%的CPU时间都花在了这个神秘的“空闲任务”上.事实上,如果CPU一直忙于正常用户,通常是出现了问题,或者是恶意软件占用了CPU。为了保持设计的一致性,操作系统开发人员创建了一个空闲任务,该任务被安排在没有其他工作可做时运行。我们在Linux启动过程中看到空闲任务是进程0,它是计算机首次开机时运行的第一条指令的直接后代。它在rest_init中初始化,其中init_idle_bootup_task初始化空闲调度类。总之,Linux支持实时进程、常规用户进程等不同的调度类,这些类在选择进程成为活动任务时按优先级顺序查询。这样,“核反应堆控制代码”总是在网络浏览器之前运行。然而,这些类通常返回NULL,这意味着它们没有合适的运行进程——它们都在休眠。但是最后运行的空闲调度类永远不会失败:它总是返回空闲任务。这一切都很好,但让我们看看这个空闲任务到底在做什么。cpu_idle_loop,如下:cpu_idle_loopwhile(1){while(!need_resched()){cpuidle_idle_call();}/*[注意:切换到其他任务。当空闲任务再次被选中运行时,我们回到这个循环。]*/schedule_preempt_disabled();}我省略了很多细节,我们稍后会仔细研究任务切换,但是如果你阅读代码,你就会明白它的要点:只要没有重新调度,即改变活动任务,CPU会一直空闲。以经过的时间来衡量,这个循环和它在其他操作系统中的同类可能是计算历史上执行次数最多的代码。对于Intel处理器,传统上保持空闲意味着运行暂停指令:native_haltstaticinlinevoidnative_halt(void){asmvolatile("hlt"::::"memory");}hlt暂停处理器中的代码执行并将其置于暂停状态。奇怪的是,全世界数以百万计的类似Intel的CPU大部分时间都处于停滞状态,即使它们在开机时也是如此。它也不是很节能,这导致芯片制造商为处理器开发更深的睡眠状态,以消耗更少的能量和更长的唤醒延迟。内核的cpuidle子系统负责利用这些节能模式。现在,一旦我们告诉CPU停止或休眠,我们就需要以某种方式唤醒它。如果您看过我之前的文章,您可能会怀疑其中涉及中断,它们也是如此。中断刺激CPU脱离暂停状态并恢复运行。所以把它们放在一起,这就是你阅读这篇文章时你的计算机正在做的大部分事情。除了定时器中断之外的中断也会让处理器再次运行。例如,如果您单击网页,就会发生这种情况:您的鼠标发出中断,其驱动程序处理它,突然一个进程可以运行,因为它有新的输入。此时need_resched()返回true并启动空闲任务以支持您的浏览器任务。这是随时间推移的空闲循环:在此示例中,内核将定时器中断编程为每4毫秒(ms)发生一次。这是滴答作响的时期。这意味着我们每秒获得250个节拍,因此节拍率或节拍频率为250Hz。这对于在Intel处理器上运行的Linux来说是典型的,而100Hz是另一个人群的最爱。这是在构建内核时在CONFIG_HZ选项中定义的。现在,对于一个空闲的CPU来说,这似乎是大量毫无意义的工作,事实确实如此。如果没有来自外部世界的新输入,CPU将继续这种地狱般的小睡,每秒唤醒250次,而您的笔记本电脑电池没电了。如果它在虚拟机中运行,我们将消耗主机CPU的电源和时钟周期。这里的解决方案是动态滴答,这样当CPU空闲时,定时器中断被停用或重新编程以发生在内核知道它将工作的地方(例如,一个进程可能有一个定时器)在5秒后到期,所以我们不能睡过去)。这也称为无滴答模式。最后,假设您的系统中有一个活动进程,例如长时间运行的CPU密集型任务。这和idle系统几乎一模一样:上图大致保持不变,只是将一个进程换成一个idletask,图是准确的。在那种情况下,每4毫秒中断一次任务没有意义:这只是操作系统抖动,减慢了您的工作量。Linux也可以在这种单进程场景??中停止固定速率的tick,即所谓的adaptive-tick模式。最终,固定利率可能会完全消失。对于一篇文章来说,这已经足够了。内核的空闲行为是操作系统难题的重要组成部分,它与我们将看到的其他复杂性非常相似,因此这有助于我们构建正在运行的内核的图片。
