By Jonathan Corbet August 4, 2023 ChatGPT assisted translation https://lwn.net/Articles/939973/
大内核锁(BKL)现在已经成为了一个遥远的记忆,但在那么多年里,它都是内核开发社区面临的一项棘手问题。然而 BKL 的终结并不意味着内核没有其他有问题的锁。近来,已经有一些关注转向了软中断锁(software-interrupt lock)或“下半部锁”(bottom half lock),因为它可能会在实时系统上导致延迟。Frederic Weisbecker 正在采取最新行动来减小这个锁的影响范围,该方法就是基于移除 BKL 时所采取的方法。
最初,Linux 内核是在单处理器系统上开发的 —— 当然可以理解,因为那时我们手头只有这种系统 —— 因此,代码在很大程度上基于这样的假设:它在 CPU 上运行,完全不存在其他的 CPU。于是 BKL 最终需要被引入,才能让 Linux 能够运行在那些产业分析师向我们保证的未来将会大行其道的多处理器机器上。它确保了只有一个 CPU 在任何给定时间内运行内核代码,从而避免了各种并发问题,但是显著地牺牲了性能,尤其是随着 CPU 数量的增加而更加明显。人们很快意识到 BKL 必须被移除。
在许多子系统中采取的方法(在 https://lwn.net/Articles/283066/ 一文中有更深入地描述)是将 BKL 下移至系统的更底层级别。不再是在调用每个驱动程序的 open()函数时就申请持有 BKL,而是修改每个驱动程序来自行获取 BKL。然后,open()函数可以在不持有 BKL 的情况下安全地得到调用了,每个驱动程序可以在需要时进行独立的审查(audit)和修复,之后可以删除其对 BKL 的使用。这种把 BKL 下移的动作将一个大问题分解成了许多较小且更易处理的问题。经过多年的努力,BKL 终于在 2011 年被移除了。
软中断(software interrupt)是一种延后执行的方法,用于执行一些是紧急但又无法直接在硬件中断上下文中执行的工作。当有这种工作要做时,子系统会通过设置一个 flag 来触发软中断;这会使得在下一个合适的时机会调用其处理程序,通常是在硬件中断处理完成后就立即调用,或在从系统调用返回到用户空间之前调用。如果处理时间过长,相关处理也可以推送到专门的 ksoftirqd 线程中。有关此机制的更详细讨论,请参见这篇文章 https://lwn.net/Articles/779738/ 以及 Weisbecker 为改进它所做的另一次尝试。
软中断有许多使用者,包括 tasklets、网络、块设备子系统、读-拷贝-更新(RCU)以及内核定时器。在某些工作场景中,软中断处理可能成为 CPU 总负载的一个重要部分;它可能会运行相当长的时间,从而对运行在用户空间中的软件导致延迟。会禁用软中断处理的那些内核代码(为了避免与处理程序的产生 race condition)会变成不可抢占的,这也会导致出现不太愉快的延迟。总之,与 BKL 一样,软中断反映了几十年前很适用但是现在存在问题的一种设计。
其中一个设计上的决策是,软中断处理程序需要互斥;在任何给定的 CPU 上,只能执行一个软中断处理程序。因此,如果块设备的软中断处理程序运行时间很长,那么网络和定时器处理程序可能就会被无限期地延迟。即使不同类型的软中断处理程序之间很少出现竞争,情况仍然是如此。没有确切的方法可以确定同时运行两个处理程序是否安全,因此人们不会这样做。
Weisbecker 的 patch set 旨在通过在定时器子系统中采用 BKL 方式的迁移到更底层实现的方法来解决这个问题。定时器函数会在内核的各个地方被放到队列里等待调用;它们往往是互相独立的,与其他软中断处理程序并不会产生并发问题。几乎所有的定时器函数都可以与其他软中断处理完全并发地运行 —— 但是这里说的是“几乎”。在没有确定每个定时器函数的安全性的情况下,使定时器处理完全独立于软中断处理可能会引入很难调试的问题。
相反,Weisbecker 采取了分成两步的方法来增加定时器处理的并发性。第一步是允许单个软中断向量在不完全禁用软中断处理的情况下被禁用。这个 patch set 的目的是允许定时器函数与其他软中断并发运行,但它们仍然不会跟彼此并发运行。通过禁用定时器事件的处理(在本地 CPU 上),定时器处理程序可以安全地重新启用软中断处理,而无需担心会再次调用它。
第二步是允许单个 timer 函数来通知到定时器(timer)子系统,说它们可以跟其他软中断处理并发运行。任何不会与软中断处理程序竞争、或者在需要时得执行自己的软中断禁用代码的定时器函数,都可以在设置其定时器事件时添加 TIMER_SOFTINTERRUPTIBLE flag 来标记。当定时器子系统看到此标志时,就会在该定时器函数运行时重新启用软中断处理。因此,如果出现更重要的工作的话,这个 timer 函数就可以被抢占。
在 patch set 中只有一个定时器函数 process_timeout()是以这种方式标记的。然而,Weisbecker 期待着“几年后”的一天内核的所有定时器函数都已经过 audit,并可以安全地与软中断处理程序并发运行;在那时,将可以完全从软中断机制中移除定时器处理。这样以来就是朝着最终消除软中断的一个小步骤。
显然,需要进行相当多的工作才能达到这一点。即使这个 patch set 也需要“更多微调”,以使可以中断的 timer 函数能够抢占其他软中断处理程序,这是解决问题的重要部分。但是,如果这项工作能够进入 mainline 的话,它可能就能代表着朝着这个方向迈出了一步。Weisbecker 现在已经尝试了几次解决软中断的问题,但没有取得太大的成功。然而最终,就像 BKL 一样,正确的方法将会被找到,长期存在的问题终将得到解决。
全文完 LWN 文章遵循 CC BY-SA 4.0 许可协议。