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

Revisiting the kernel's preemption models (part 1)

By Jonathan Corbet
September 21, 2023
ChatGPT translation
https://lwn.net/Articles/944686/

Ankur Arora 原本只是希望使用这个补丁集来加快在x86系统上清除huge page (大页面)的过程。结果反而是引起了广泛的关于在内核中正确管理抢占的困难的讨论。这可能会导致内核目前提供的各种抢占模型(preemption model)的一些变化。

快速内存清零(Fast memory clearing)

这个补丁集添加了一个函数,使用x86字符串指令更快地清除大量内存;这个改动在某些情况下(例如页面错误处理,page-fault handling)会带来一些性能改进,因为它必须要先将一段内存区域清零然后才能交付给发生fault的进程去使用。但是,这种方法有一个小问题:虽然能够使用单个指令清除大范围的内存很方便,但这一指令可能执行很长时间—足够长到让在同一CPU上运行的其他进程感受到出现不应该有的延迟。

由于长时间运行操作引起的额外延迟,对内核来说并不是一个新问题。通常的处理方式是分割这些操作,插入 cond_resched() 调用,如果有更高优先级的进程需要运行,则主动放弃CPU一段时间。但是,不可能将这些调用插入单个长时间运行的指令中去,因此需要一些其他的弥补措施。Arora选择添加了一个新的任务标志(task flag TIF_ALLOW_RESCHED ),从而标记当前任务为可抢占的。如果内核在处理中断结束时看到该标志,它就知道如果需要的话可以切换到更高优先级的任务。这个新标志可以在清除页面之前先设置好,然后在之后再清除掉。

这种机制后来发现存在一些问题。设置该标志的代码可能是可抢占的(preemptible),但它调用的其他函数可能不是。其他还有一些事件比如硬件中断或CPU trap(例如page fault),也可能使内核处于不可抢占状态。设置一个标志来标记当前任务为可抢占的情况不太合适。

看起来要使这个想法在当前的内核中起作用,需要摆脱任务标志,而是标记代码的特定范围为可抢占。然而,这可能会限制该功能的广泛使用,因为要找出当前位置是否可抢占,需要维护可抢占范围的数据结构并在中断处理过程中来搜索以查看是否可能进行抢占。这让Linus Torvalds有些不太高兴:

我真的很讨厌这一点,因为我希望我们能够使用这个功能来减少那些让人讨厌和随机的"cond_resched()"调用。[…] 我希望我们能够有一种通用的方式来处理这个问题,其中我们可以说"这个东西是可重新调度的",并且摆脱(或者至少不再增加)cond_resched()带来新的杂乱代码。

Peter Zijlstra 指出,Torvalds描述的是全抢占,而内核已经相当好地支持了这种模式。这导致了讨论的一些转变。

抢占模型(Preemption models)

传统的Unix模型根本不允许内核被抢占;一旦内核获取CPU,它将一直执行,直到它自愿放弃CPU。最初,Linux也遵循这一模型;然而,多年来,内核里支持了其他一些可以通过config选项来选择的抢占模式:

  • PREEMPT_NONE 是传统模型,根本没有抢占。内核必须通过返回到用户空间、阻塞操作或 cond_resched() 调用来放弃CPU,然后其他任务才能运行。

  • PREEMPT_VOLUNTARY 增加了内核被认为自愿放弃CPU的位置的数量(显著增加)。例如,每次调用 might_sleep() (通常是用来标记可能会阻塞的函数的调试函数)都成为一个抢占点。

  • PREEMPT 是全抢占;内核可以在任何不明确阻止抢占的地方被抢占(例如,持有自旋锁或明确禁用抢占的情况除外)。

  • PREEMPT_RT 是实时抢占模式,甚至大多数自旋锁也可以被抢占,并且还进行了其他一些改动。

这些选项代表了吞吐量和延迟之间的不同权衡。抢占不是没有成本的;它可能会导致cache情况恶化,以及跟踪在任何给定时间知道抢占是否安全所需的状态本身也会有成本。但延迟也很重要,尤其是对于交互式使用。在其中一个极端的 PREEMPT_NONE 这里,最关注吞吐量,而延迟就可能会很长。随着抢占级别的提高,延迟减少,但吞吐量也可能下降。

并且还有一点更加增加了复杂性,就是新增了另一个选项, PREEMPT_DYNAMIC ,于2021年由Michal Hocko在5.12内核中添加。它允许将抢占选择推迟到启动时,其中除了 PREEMPT_RT 之外的任何模式都可以通过 preempt= 命令行参数选择。 PREEMPT_DYNAMIC 允许发行商提供单个内核,但又可以让用户选择最适合其工作负载的抢占模式。

Torvalds似乎是第一次仔细看 PREEMPT_DYNAMIC ,观察到即使在没有抢占模式下运行时,它仍然保留了有关当前任务是否可抢占的所有信息。正如Zijlstra 回应 的那样,这表明维护该信息的开销似乎不被视为问题;Ingo Molnar 补充说,虽然将该开销删除可能会很好,但是""对于几乎所有x86 CPU类型都有一种可怕的安全漏洞的开销不过是大多数CPU上的噪声。" 他说,这种开销不如抢占导致""某些特定企业客户关心的关键基准测试的随机子集发生实质性变化""重要,因此 PREEMPT_DYNAMIC 适用于当前情况。

Zijlstra 还表示,由于 PREEMPT_DYNAMIC 似乎对发行版提供商很方便,他愿意删除其他选项。尽管在对话中没有提到这一点,这样做可能也会解决最初的问题。如果内核总是维护了知道抢占何时安全所需的信息,那么可以使用这些信息来实现 TIF_ALLOW_RESCHED 的安全实现。不过可能不会这么做;讨论仍在进行中,正在考虑对抢占进行更多的重大修改;请继续关注本系列的第二部分,待局势稍微稳定下来后再详细介绍。

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

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

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

format,png

Logo

欢迎大家加入成都城市开发者社区,“和我在成都的街头走一走”,让我们一起携手,汇聚IT技术潮流,共建社区文明生态!

更多推荐