LWN:走向延迟抢占的漫漫长路!

关注了就能看到更多这么棒的文章哦~

The long road to lazy preemption

By Jonathan Corbet
October 18, 2024
Gemini-1.5-flash translation
https://lwn.net/Articles/994322/

内核的 CPU 调度程序目前提供了多种抢占模式,这些模式在系统吞吐量和响应时间之间实现了多种权衡。早在 2023 年 9 月的一次 关于调度的讨论 催生了 “延迟抢占(lazy preemption)” 的概念,该概念可以简化内核中的调度,同时提供更好的结果。后来这个领域静悄悄了一段时间,但延迟抢占以 Peter Zijlstra 的 补丁系列 的形式回来了。虽然这个概念似乎运作良好,但仍有大量工作要做。

一些回顾

当前的内核有四种不同的模式,用来控制一个任务何时可以被抢占以利于另一个任务运行。 PREEMPT_NONE 是最简单的模式,它只允许在运行的任务用完其时间片时发生抢占。 PREEMPT_VOLUNTARY 在内核中添加了大量可以发生抢占的点,如果需要的话。 PREEMPT_FULL 允许在几乎所有地方发生抢占,除了内核中禁止抢占的地方,比如当持有自旋锁时。最后, PREEMPT_RT 将抢占置于其他所有事情之上,甚至使大多数持有自旋锁的代码可抢占。

更高的抢占级别使系统能够更快地响应事件;无论事件是鼠标的移动还是来自核反应堆的“即将熔毁” 信号,更快的响应往往都是更让人满意。但是,更高的抢占级别可能会损害系统的整体吞吐量;具有大量长时间运行、CPU 密集型任务的工作负载往往在尽可能少的干扰下工作得更好。更频繁的抢占也可能导致更高的锁竞争。这就是为什么存在不同模式的原因;最佳的抢占模式将因不同的工作负载而异。

大多数发行版都发布了使用 PREEMPT_DYNAMIC 伪模式构建的内核,该模式允许在启动时选择前三种模式中的任何一种,其中 PREEMPT_VOLUNTARY 是默认模式。在安装了 debugfs 的系统上,当前模式可以从 /sys/kernel/debug/sched/preempt 读取。

PREEMPT_NONE 和 PREEMPT_VOLUNTARY 不允许任意抢占在内核中运行的代码;有时,即使在没有优先考虑最小延迟的系统中,这也会导致过度的延迟。这个问题是内核中存在大量工作可以完成的地方造成的;如果允许这些工作不受控制地运行,它们会扰乱整个系统的调度。为了解决这个问题,在长时间运行的循环中散布了对 cond_resched() 的调用,每个调用都是一个额外的自愿抢占点,即使在 PREEMPT_NONE 模式下也是有效的。内核中有数百个这样的调用。

这种方法存在一些问题。 cond_resched() 是一种启发式方法,它只在开发者认为应该放置它的位置起作用。一些调用肯定是多余的,而内核中将有其他地方可以从 cond_resched() 调用中受益,但却没有。 cond_resched() 的使用,从本质上来说,做出了一个应该限制在调度代码中的决定,并将其扩展到整个内核。简而言之,这是一种大部分情况下有效的临时改法,但可以做得更好。

做得更好

跟踪给定任务在任何时刻是否可以被抢占是一件复杂的事情,它必须考虑多个变量;有关详细信息,请参见 这篇文章 和 这篇文章。其中一个变量是一个简单的标志 TIF_NEED_RESCHED,它指示存在一个正在等待访问 CPU 的更高优先级任务。例如,唤醒一个高优先级任务会导致该标志在当前正在运行的任务中被设置。在没有此标志的情况下,内核不需要考虑抢占当前任务。

内核可以在多个地方注意到该标志,并导致当前运行的任务被抢占。调度程序的 timer tick 就是一个例子;一个任务从系统调用在任何情况下返回到用户空间都是另一个很好的例子。中断处理程序的完成是另一个,但这种检查(它可以导致在启用中断的任何时候发生抢占)只在 PREEMPT_FULL 内核中启用。调用 cond_resched() 也会检查该标志,如果设置了该标志,它会调用调度程序以将 CPU 让给另一个任务。

延迟抢占补丁的核心很简单;它们添加了另一个标志 TIF_NEED_RESCHED_LAZY ,它表示需要在某个时刻重新调度,但不一定是立即。在延迟抢占模式 ( PREEMPT_LAZY ) 中,大多数事件会设置新的标志而不是 TIF_NEED_RESCHED 。在像从内核返回用户空间这样的点,任何一个标志都会导致调用调度程序。然而,在自愿抢占点和中断返回路径中,只检查 TIF_NEED_RESCHED 。

这种变化的结果是,在延迟抢占模式下,内核中的大多数事件不会导致当前任务被抢占。但是,该任务 应该 最终被抢占。为了实现这一点,内核的计时器滴答处理程序将检查是否设置了 TIF_NEED_RESCHED_LAZY ;如果是,则 TIF_NEED_RESCHED 也会被设置,可能导致正在运行的任务被抢占。任务通常会运行接近其完整时间片,除非它们自愿放弃 CPU,这应该会导致良好的吞吐量。

通过这些更改,延迟抢占模式可以像 PREEMPT_FULL 一样,在(几乎)所有时候都启用内核抢占。抢占 可以 在抢占计数器指示应该抢占的任何时候发生。这允许长时间运行的内核代码在其他条件不阻止它时被抢占。它还允许抢占在真正需要的时候快速发生。例如,如果一个实时任务成为可运行的,例如,作为处理中断的结果, TIF_NEED_RESCHED 标志将被设置,导致几乎立即抢占。在这种情况下,不需要等待计时器滴答。

然而,如果只设置了 TIF_NEED_RESCHED_LAZY ,抢占 不会 发生,这种情况在大多数时候都会发生。因此, PREEMPT_LAZY 内核不太可能抢占正在运行的任务,而 PREEMPT_FULL 内核则更可能抢占正在运行的任务。

最终移除 cond_resched()

这项工作的最终目标是拥有一个调度程序,它只有两种非实时模式: PREEMPT_LAZY 和 PREEMPT_FULL 。延迟模式将占据 PREEMPT_NONE 和 PREEMPT_VOLUNTARY 之间的位置,取代它们两者。然而,它不需要为它取代的两种模式而添加的自愿抢占点。由于抢占现在几乎可以在任何地方发生,因此不再需要在特定位置启用它。

但是,目前, cond_resched() 调用仍然存在;如果别无他法,只要 PREEMPT_NONE 和 PREEMPT_VOLUNTARY 模式存在,它们就是必需的。这些调用还有助于确保在延迟抢占稳定时不会引入问题。

在当前的补丁集中, cond_resched() 只检查 TIF_NEED_RESCHED ,这意味着抢占将在许多情况下被推迟,而在 PREEMPT_VOLUNTARY 或 PREEMPT_NONE 模式下,抢占将立即从 cond_resched() 中发生。Steve Rostedt 质疑 了这一变化,询问 cond_resched() 是否应该保留其旧的含义,至少对于 PREEMPT_VOLUNTARY 情况而言。他认为,即使 PREEMPT_VOLUNTARY 预计最终会被移除,保持旧的行为可以帮助简化过渡。

Thomas Gleixner 回答 说,只检查 TIF_NEED_RESCHED 是正确的选择,因为它将有助于完全移除 cond_resched() 调用:

这迫使我们查看所有这些调用,并确定是否需要将它们扩展为包含延迟位。当 LAZY 生效时,不需要延迟位的那些调用可以被消除,因为一旦在滴答中设置了非延迟位,这将在下一个可能的抢占点抢占。

他补充说,他预计 ""少于 5%"" 的 cond_resched() 调用需要检查 TIF_NEED_RESCHED_LAZY,因此,即使在完全过渡到 PREEMPT_LAZY 之后,它们也需要保留。

但是,在此之前,有数百个 cond_resched() 调用需要检查,并且至少对于大多数调用而言,需要移除。许多其他细节也需要处理;Ankur Arora 的 补丁集 解决了其中一些问题。当然,还需要进行广泛的性能测试;Mike Galbraith 已经 早早开始了 这项工作,结果表明,使用延迟抢占的吞吐量仅略低于使用 PREEMPT_VOLUNTARY 的吞吐量。

所有这些加起来仍然需要做很多工作,但延迟抢占工作的最终结果应该是一个内核,该内核更小、更简单,同时提供可预测的延迟,而无需在代码中散布与调度程序相关的调用。这似乎是一个更好的解决方案,但实现它需要一些时间。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

edb163d7e85c853891db97330cf577c9.jpeg

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值