简介

原文链接:https://lwn.net/Articles/799454/

本文记录了2019年的 Linux Plumbers 会议上针对核调度(core scheduling)的一些讨论,涉及实现、使用场景、优劣势,以及一些未来可能的改进方向。

译文

一些内核的新特性会非常受社区欢迎,另一些则不然。公平的说核调度属于后者,由于对一个核上同时运行哪些任务做出了限制,它让CPU调度变得更为复杂了。在2019年的 Linux Plumbers 会议上,核调度 是(至少)三个不同议程的主题。其中最有趣的一个结论,可能就是用于保护旁路攻击(side-channel attacks)的使用场景。

目前的状态

讨论从Julien Desfossez和Vineeth Remanan Pillai的对话开始。Desfossez指出核调度已经开发了大约一年;它的首要目标是确保同步多线程(SMT,或“超线程”)在有类似推测执行(speculative-execution)这类漏洞的硬件上仍然是安全的。一个SMT核包含两个或更多的CPU(有时也称为“硬件线程”),他们共享着大量的底层硬件。这里的共享也包含一些缓存,这让SMT特别容易受到基于缓存的旁路攻击。对于那些担忧此类攻击的网站,目前唯一的手段就是关闭SMT,但这会对某些负载造成严重的性能下降。而核调度则是一种相对保守的方法,它确保同一核上同一时间运行的任务是彼此信任的。

Pillai此时接过了话题,他表示核调度把彼此信任的任务聚到了一个核上。它以SMT核上的CPU为单位,在这些核间CPU上寻找优先级最高的任务;这个任务随后主导调度决策。如果找到另一个和高优先级任务相兼容的任务,它就可以在其中一个CPU上运行;否则这些CPU都会被迫进入空闲状态。

最初核调度是针对KVM实现的;它只允许同一虚拟机的虚拟CPU线程共享一个核。随后它变得更为通用,可以由管理员来配置哪些任务是互相信任的。最初的原型利用了控制群组(control groups);管理员可以通过把任务放进同一群组来标记他们是互相兼容的,或者通过在多个群组之间配置相同的cpu.tag来实现。目前第三个版本还在讨论中;主要是关注bug修复和性能问题。

这些问题其实并不多。调度器通常会比较任务的虚拟运行时间(vruntime)来进行调度决策;这在单个CPU上是可行的,但在多个CPU之间就不合适了。会导致一些任务的饥饿。目前的思路是把这种CPU之间的比较作为一个负载平衡问题,或许可以通过创造一个归一化的、或核间的虚拟运行时间来提高比较的准确性。

强制核间CPU进入空闲状态是核调度无法避免的缺陷,而且显然比我们想象的还要严重。具体来说,一个CPU计算密集型的任务可以导致核间CPU长时间的空闲,让那些想在上面执行的进程CPU饥饿。调度器必须要以某种方式学会统计强制空闲的时间并及时作出决策(切换饥饿的任务上来运行)。或者是创建一个特殊的空闲任务,它会运行在强制空闲的CPU上并在需要切换的时候提醒调度器。

Desfossez接着介绍了一下测试的情况,针对这个补丁集的测试是广泛的。基于性能的补丁总是需要展示明显的收益;在这个例子中,很明显的核调度无论如何都比关闭SMT要好。测试基建也通过追踪的手段证实了没有不兼容的任务跑在一个核上。

测试揭示了上述的公平性问题,有些负载在关闭SMT后确实表现的更好了。具体来说,I/O密集型的负载并不会受益于核调度; 他提到了在一个MySQL的基准测试中,打开核调度导致了性能下降。

未来我们需要重新思考一下进程的选择。还有个小问题,尽管核调度保护了进程免受其他进程的攻击,对于来自用户态的针对内核的攻击却无能为力。要解决这个问题需要在虚拟机进行系统调用和陷入内核的时候增加同步点,开销可能很大。然而我们也没有其他手段来保护内核免于MDS攻击。最后他说,需要重新考虑一下用于标识进程的接口。

讨论过后,Len Brown问当SMT核有超过两个CPU时会怎么样。虽然目前还没有这样的系统,但可以想像CPU设计者们一定有在考虑这些。他得到的回答是这个特性是通用的,适用于这类场景,但无法确认因为没有这样的机器可以做测试。

其他使用场景

在调度器小会上,核调度也是其他一系列议程的主题,它们更多的是关注使用场景,而不是具体的实现细节。Subhra Mazumdar描述了Oracle的一个数据库使用场景,他们有自己的虚拟化配置并希望利用核调度来优化任务的分布。但使用后却出现了明显的性能下降(17-30%),基本上就是由于强制空闲导致的。尽管其他核上有适合它运行的任务,CPU仍然会进入空闲状态,且频繁发生。Mazumdar建议改造调度器的唤醒路径,允许这样的CPU到其他核上找一个符合标签的任务。

在讨论中,核调度不可能适用于所有负载这一点被反复提及。具体来说,参考Mel Gorman的基准测试,在没有核调度的情况下,单单打开SMT也会导致性能下降。

Aubrey Li 提到了另一种核调度的应用场景:深度学习训练。这类负载会大量执行AVX-512指令,带来明显的性能提升。但这类指令会降低整个核的CPU最大频率;如果其他不相关的任务跑到了这个核上,就会受到AVX-512指令的不利影响。所以让两个使用AVX-512指令的任务跑同一个核,总不会比只跑一个要差。

所以应该要让大量使用这类指令的任务跑在同一个核上。核调度能做到这一点;使用之后他的负载得到了10%的吞吐量提升和30%的延迟下降。因此他相信把核调度合入主线是有价值的。

Jan Schönherr,带来了又一个使用场景:隔离一些进程,而让其他的跑在一起。他的补丁集有点混淆(在这个上下文中),叫做coscheduling,允许管理员来配置策略,强制一些相关的进程跑在同一个核上,并排除其他所有任务。核调度因此提升了安全性,且由于相关进程共享了CPU资源,性能也得到了提升。

他询问是否现有的cpuset机制也能处理这个问题。得到的答案是可以,但只有当系统还空闲的时候。如果负载太高的话就会失去同步执行的特性。

实时

一天后,在实时小会上,Peter Zijlstra组织了另一个关于核调度的议程,他称之为“太火了”。太多的人想要这个特性让他担心除了合并以外没有其他的选择,尽管这并不是一个针对旁路问题的完整方案。

实时开发者们也对这个点子很感兴趣。从实时的角度来看,SMT的不可预知是个问题,照Zijlstra的话来讲,“完全无法预测”。实时用户通过关闭SMT来避免它所导致的延迟问题。但核调度可以做到当一个实时进程在核上运行时,强制其他核间CPU进入空闲状态,这避免了SMT干扰。一扇新的大门打开了,当核上没有实时任务的时候使能SMT,而在有实时需求的时候快速关闭,让两者都受益。

然而这样利用核调度引入了更多的疑问;议程讨论了其中一个:关于核调度对期限调度(deadline scheduler)的准入控制(admission control)所造成的影响。准入控制让调度器在CPU资源无法满足任务期限的情况下拒绝接受期限任务。强制CPU空闲会影响总的CPU可用时间;如果准入控制不把这个算进来的话,系统可能会接受超过其处理能力的任务量。

议程讨论出的其中一个可能的解决方案,就是把期限任务的最差执行时间(实质上被允许执行时间)倍乘核间CPU的数量,因为事实上这个任务运行时会占据所有的核间CPU。然而有很多细节需要处理,例如如何给这类任务设置一个标签;利用控制群组或者prctl()可能太晚了,恐怕赶不上准入控制的制定。或许可以强化sched_setattr()来达到这个目的,但这会导致核调度使用两种不同的打标方式。Zijlstra认为开发者应当找一个通用的接口来服务所有这些用例。

合并到主线

在调度器小会上,Pillai总结了议程:核调度对某些用例非常有价值,主线内核应当包含这个特性来服务那些受益的用户。虽然这个特性默认需要关闭,因为并非所有人都能受益。核调度无法保护内核是一个小问题;Pillai断言在虚拟机陷入内核时增加一个安全边界就足够了。隔离系统调用和中断并不那么重要。然而Thomas Gleixner非常不同意这个观点,说无论哪种机制下,进入内核的入口都应当是一致的。

Paul Turner说针对硬件漏洞的保护不只是一个调度问题,而且在这点上核调度也是不足的。他认为coscheduling也会是必要的,可能还需要类似地址空间隔离这类注定是有问题的(目前为止)补丁。所有这些都需要看一下,开发者需要找到一个方法把他们整合起来。Gleixner认可这一说法,但表示也需要有大局观,不然这些碎片永远都放不到一起。

译者感想

译者数年前曾经接触过同步调度的理念,当时是为了解决虚拟机内部spinlock空转的问题而想要把所有的vcpu进行同步调度,性能提升效果明显,但CPU数量多的时候,由于需要通信来进行同步,出现了scalability问题,之后也就没有继续推进。

若干年后核调度用类似的理念来解决旁路攻击问题,相对于关闭SMT即便有性能损耗也变得可以接受,同一个idea从完全不同的角度得到了大家认可,不禁让人感叹天时之重要。