为什么在 Linux 中 spin_lock 和 spin_unlock 之间禁用中断?

Why is interrupt disabled between spin_lock and spin_unlock in Linux?

我正在阅读 Linux 信号量的实现。由于原子性,signal和wait(源码中的up和down)使用了自旋锁。然后我看到 Linux 在 spin_lock_irqsave 中禁用了中断并在 spin_unlock 中重新启用了中断。这让我很困惑。在我看来,在关键部分禁用中断确实没有意义。

例如,proc A(当前处于活动状态)获得了锁,proc B(已阻塞)正在等待锁,proc C 正在做一些不相关的事情。在 A 和 B 之间的临界区内将上下文切换到 C 是非常有意义的。即使 C 也尝试获取锁,因为锁已经被 A 锁定,结果将是 C 被阻塞并且 A 恢复执行。

因此,我不知道为什么 Linux 决定在自旋锁保护的临界区内禁用中断。它可能不会造成任何问题,但对我来说似乎是一个多余的操作。

但是如果中断想要向等待线程发出信号怎么办?或者想测试 sempahore 值?这里的 irq 禁用不是为了防止两个进程之间的上下文切换,而是为了防止 irq。都在文件开头的注释中:

  /*
   * Some notes on the implementation:
   *
   * The spinlock controls access to the other members of the semaphore.
   * down_trylock() and up() can be called from interrupt context, so we
   * have to disable interrupts when taking the lock.  It turns out various
   * parts of the kernel expect to be able to use down() on a semaphore in
   * interrupt context when they know it will succeed, so we have to use
   * irqsave variants for down(), down_interruptible() and down_killable()
   * too.
   *
   * The ->count variable represents how many more tasks can acquire this
   * semaphore.  If it's zero, there may be tasks waiting on the wait_list.
   */

首先声明一下,我不是Linux专家,所以我的回答可能不是最准确的。请指出您可能发现的任何缺陷和问题。

想象一下,如果一些共享数据被内核的各个部分使用,包括需要快速且不能阻塞的中断处理程序等操作。假设系统调用 foo 当前处于活动状态并且已获取 use/access 共享数据 bar 的锁,并且未禁用中断 when/before 获取所述锁。

现在是一个(硬件)中断处理程序,例如键盘启动,还需要访问 bar(硬件中断的优先级高于系统调用)。由于 bar 当前被系统调用 foo 锁定,中断处理程序无法执行任何操作。中断处理程序确实需要快速且不会被阻塞,因此它们只是在尝试获取锁时继续旋转,这将导致 死锁 (即系统冻结),因为系统调用 foo 永远没有机会完成并释放它的锁。

如果您在尝试获取 foo 中的锁之前禁用中断,那么 foo 将能够完成它正在做的任何事情并最终释放锁(并恢复中断)。在 foo 持有自旋锁时尝试进入的任何中断都将留在队列中,并且能够在释放锁时启动。这样,您就不会 运行 陷入上述问题。但是,还必须注意确保 bar 的锁保持时间尽可能短,以便其他更高优先级的操作可以在需要时接管。

答案很简单:尝试获取锁的线程无法知道中断它的 ISR 是否会尝试获取相同的锁。如果发生这种情况,ISR 将永远在同一个锁上旋转,系统将死锁。