围绕 spin_lock_irqsave 的困惑:在什么嵌套情况下中断状态被保留?

Confusion around spin_lock_irqsave: in what nested situation is interrupt state kept?

关于自旋锁的问答有很多,但我还是一头雾水。我认为这是因为问题和答案采用不同的设置,或者在他们询问或回答时没有清楚地解释有关它是 SMP 还是抢占式内核的设置(并且也混入了一些旧信息)。

我的第一个问题是:(Q1) 在 SMP 情况下,是 schedule() 运行 在每个处理器上并发(我知道调度由 jiffies 开始定时器中断)?我会在下面的问题中假设是。如果有人能简要地向我解释进程在调度期间如何在处理器内核之间移动,我将不胜感激。

我正在尝试了解如何、为何、何时使用 spin_lock/unlock_irqsave。这是我的问题。

假设有一段代码调用spin_lock_irqsave,调用spin_lock_irqsave()时中断状态(enable)为'disable'。这段代码可以在中断上下文中 运行ning 吗?可能不是,因为如果在相应的本地处理器中禁用中断,ISR 不应该首先启动。因此,调用 spin_lock_irqsave 的代码必须在进程上下文中。好的,中断之前已被禁用,但是一个进程正在尝试使用 spin_lock_irqsave.

锁定

什么情况下可以禁止中断?我认为有两种情况。

情况 1:先前的中断例程已被此进程抢占(正在调用此 spin_lock_irqsave)。这很奇怪,因为 ISR 不能被抢占。 (Q2) 对了,在抢占式内核中,ISR可以被进程抢占吗? (Q3) 我猜是因为 preempt_count()(current_thread_info()->preempt_count) 一样 #defined,所以 preempt_disable 只对进程有效,对中断无效。中断也有当前线程信息吗?

情况 2:先前的正常进程已使用 spin_lock_irq(或 irqsave)获取锁。但这也很奇怪,因为在锁定之前,spin_lock_irq(或irqsave)禁用任务的抢占和中断,告诉调度程序在调度程序定时器中断后不要切换到其他任务。所以这个案子不可能是真的。

我知道我必须进一步了解 SMP 的进程调度和内核抢占,也许我误解了什么。有人可以解决我的问题吗?非常感谢阅读。

There are many Q&As about spinlocks, but it's still confusing to me. I think it's because the questions and answers assume different settings or not clearly explain the settings about if it's SMP or if it's preemptive kernel or not when they ask or answer (and some old info is mixed in too).

我只能同意。自旋锁虽然本质上很简单,但是当包含在现代 Linux 内核的上下文中时,它根本不是一个简单的主题。我认为您无法仅通过阅读随机的和特定于案例的 Stack Overflow 答案来很好地理解自旋锁。

我强烈建议您阅读 Chapter 5: Concurrency and Race Conditions of the book Linux Device Drivers,它可以在线免费获得。特别是,第 5 章的 "Spinlocks" 部分非常有助于理解自旋锁在不同情况下的用途。

(Q1) in SMP situation, is schedule() run on every processor concurrently? [...] I would appreciate it if someone could briefly explain it to me how processes move processor cores during scheduling too.

是的,你喜欢也可以这么看。每个CPU(即每个处理器核心)都有自己的计时器,当在给定的CPU上引发计时器中断时,CPU执行内核注册的计时器中断处理程序,它调用调度程序,重新调度进程。

系统中的每个 CPU 都有自己的 运行queue,其中包含处于 运行nable 状态的任务。任何任务最多可以包含在一个 运行 队列中,并且不能同时 运行 在多个不同的 CPU 队列中。

任务的 CPU 亲和力决定了任务可以 运行 在哪个 CPU 上。默认的 "normal" affinity 允许任务在任何 CPU 上 运行(特殊配置除外)。根据它们的相似性,任务可以从一个 运行 队列移动到另一个队列,或者通过调度程序或者如果他们需要的话通过 sched_setaffinity 系统调用(这里的 解释了如何)。

建议阅读:A complete guide to Linux process scheduling

Suppose there is a code which calls spin_lock_irqsave, and the interrupt state (enable) was a 'disable' at the time of calling spin_lock_irqsave(). Could this code be running in interrupt context? Probably not.

为什么不呢?这个有可能。代码可以在中断上下文中 运行ning,但不会被 different 中断调用。请参阅我的答案底部。

Case 1: a previous interrupt routine had been preempted by this process (which is calling this spin_lock_irqsave). This is weird because ISR cannot be preempted.

你是对的,这很奇怪。更奇怪的是,这 不可能 。在 Linux 上,任何时候都可以启用或禁用中断(没有中间值)。中断并没有真正的 "priority"(就像任务一样),但我们可以将它们分为两类:

  • 非抢占式中断必然需要运行从头到尾完全控制CPU。这些中断将系统置于 "disabled interrupts" 状态,并且不会发生其他中断。
  • 可重入并允许其他中断发生的抢占式中断。如果在处理此中断时发生另一个中断,您将进入嵌套中断场景,这类似于任务的嵌套信号处理程序场景。

在您的情况下,由于中断之前已被禁用,这意味着如果禁用它们的代码是一个中断,那么它是一个不可抢占的中断,因此它不可能被抢占。它也可能是一个抢占式中断,它正在执行需要禁用中断的代码的关键部分,但情况仍然相同,你不能在 another 中断中。

(Q2) By the way, in preemptive kernel, can ISR be preempted by a process?

没有。说"preempted by a process"是不合适的。进程并没有真正抢占任何东西,它们被控制的内核抢占。就是说,抢占式中断 理论上可以 被另一个例如由进程注册的中断中断(不幸的是,我不知道这种情况的示例)。不过,我仍然不会将其称为 "preempted by a process",因为整个事情一直在内核 space.

中发生

(Q3) [...] Do interrupts also have the current thread info?

中断处理程序生活在不同的世界,他们不关心 运行ning 任务,也不需要访问此类信息。如果你真的想要 current 甚至 current_thread_info ,你可能会得到 ,但我怀疑这对任何事情都有帮助。中断不与任何任务关联,中断与某个任务运行ning之间没有link。另一个答案here供参考

Case 2: a previous normal process had acquired the lock with spin_lock_irq (or irqsave). But this also is weird because before locking, spin_lock_irq (or irqsave) disables preemption and interrupt for the task telling the scheduler not to switch to other task after the scheduler timer interrupt. So this case cannot be true.

是的,你是对的。这不可能。


spin_lock_irqsave() 函数的存在是为了在您不知道中断是否已经被禁用的情况下使用,因此您不能使用 spin_lock_irq() 后接 spin_unlock_irq() 因为第二个函数将强行重新启用中断。顺便说一句,我在上面 link 编辑的 Linux 设备驱动程序的第 5 章中也对此进行了解释。

在您描述的场景中,您正在调用 spin_lock_irqsave() 并且中断已被其他东西禁用。这意味着任何最终调用当前函数的父调用函数必须已经以某种方式禁用了中断。

可能出现以下情况:

  1. 最初的中断禁用是由中断处理程序引起的,您现在正在执行另一段代码作为同一中断处理程序的一部分(即当前函数已被中断处理程序本身直接或间接调用)。您可以很好地在中断处理程序调用的函数中调用 spin_lock_irqsave()。或者甚至只是对 local_irqsave() 的调用(例如 kfree() 函数就是这样做的,它肯定可以从中断上下文中调用)。

  2. 最初的中断禁用是由正常的内核代码引起的,您现在正在执行另一段代码作为同一正常内核代码的一部分(即当前函数在禁用中断后被其他内核函数直接或间接调用)。这是完全可能的,事实上这就是 irqsave 变体存在的原因。