在 SMP 机器上,我们必须使用 spin_lock_irqsave
而不是spin_lock_irq
来自中断上下文。
为什么我们要保存标志(包含 IF)?
是否有另一个中断程序可以中断我们?
在 SMP 机器上,我们必须使用 spin_lock_irqsave
而不是spin_lock_irq
来自中断上下文。
为什么我们要保存标志(包含 IF)?
是否有另一个中断程序可以中断我们?
spin_lock_irqsave
主要用于在获取自旋锁之前保存中断状态,这是因为自旋锁在中断上下文中获取锁时禁用中断,并在解锁时重新启用它。中断状态被保存,以便它应该再次恢复中断。
例子:
spin_lock_irq
将禁用中断 x 并获取锁spin_unlock_irq
将启用中断 x。因此,在释放锁后的第三步中,我们将启用中断 x,该中断在获取锁之前被禁用。
因此,只有当您确定没有禁用中断时,您才spin_lock_irq
应该使用spin_lock_irqsave
.
If interrupts are already disabled before your code starts locking, when you call spin_unlock_irq
you will forcibly re-enable interrupts in a potentially unwanted manner. If instead you also save the current interrupt enable state in flags
through spin_lock_irqsave
, attempting to re-enable interrupts with the same flags
after releasing the lock, the function will just restore the previous state (thus not necessarily enabling interrupts).
Example with spin_lock_irqsave
:
spinlock_t mLock = SPIN_LOCK_UNLOCK;
unsigned long flags;
spin_lock_irqsave(&mLock, flags); // Save the state of interrupt enable in flags and then disable interrupts
// Critical section
spin_unlock_irqrestore(&mLock, flags); // Return to the previous state saved in flags
Example with spin_lock_irq
( without irqsave ):
spinlock_t mLock = SPIN_LOCK_UNLOCK;
unsigned long flags;
spin_lock_irq(&mLock); // Does not know if interrupts are already disabled
// Critical section
spin_unlock_irq(&mLock); // Could result in an unwanted interrupt re-enable...
对Beyond的需要与需要Beforespin_lock_irqsave
的spin_lock_irq
原因非常相似。这是 Robert Love 的 Linux Kernel Development Second Edition 对这一要求的一个很好的解释。local_irq_save(flags)
local_irq_disable
如果在调用之前已经禁用中断,则 local_irq_disable() 例程是危险的。对 local_irq_enable() 的相应调用无条件地启用中断,尽管它们一开始就关闭了。相反,需要一种机制来将中断恢复到先前的状态。这是一个常见的问题,因为在启用和不启用中断的情况下都可以访问内核中的给定代码路径,具体取决于调用链。例如,假设前面的代码片段是一个更大函数的一部分。想象一下,这个函数被另外两个函数调用,一个禁用中断,一个不禁用。因为随着内核大小和复杂性的增长,要知道通向函数的所有代码路径变得越来越困难,在禁用它之前保存中断系统的状态要安全得多。然后,当您准备好重新启用中断时,您只需将它们恢复到原始状态:
unsigned long flags;
local_irq_save(flags); /* interrupts are now disabled */ /* ... */
local_irq_restore(flags); /* interrupts are restored to their previous
state */
请注意,这些方法至少部分作为宏实现,因此 flags 参数(必须定义为无符号长整数)似乎是按值传递的。此参数包含特定于体系结构的数据,其中包含中断系统的状态。因为至少一个受支持的体系结构将堆栈信息合并到值中(咳咳,SPARC),所以不能将标志传递给另一个函数(具体来说,它必须保留在同一个堆栈帧上)。因此,调用 save 和调用 restore 中断必须发生在同一个函数中。
所有前面的函数都可以从中断和进程上下文中调用。
阅读为什么在中断上下文中执行的内核代码/线程不能休眠?链接到 Robert Loves文章,我读到:
一些中断处理程序(在 Linux 中称为快速中断处理程序)在本地处理器上的所有中断被禁用的情况下运行。这样做是为了确保中断处理程序尽可能快地运行而不会中断。更重要的是,所有中断处理程序都在所有处理器上禁用其当前中断线的情况下运行。这确保了同一中断线的两个中断处理程序不会同时运行。它还可以防止设备驱动程序编写者必须处理递归中断,这会使编程复杂化。
下面是 linux 内核 4.15.18 中的部分代码,显示 spiin_lock_irq() 将调用 __raw_spin_lock_irq()。但是,它不会保存任何标志,正如您在下面的部分代码中看到的那样,而是禁用中断。
static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
local_irq_disable();
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
下面的代码显示了 spin_lock_irqsave(),它保存了当前阶段的标志,然后抢占禁用。
static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
{
unsigned long flags;
local_irq_save(flags);
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
/*
* On lockdep we dont want the hand-coded irq-enable of
* do_raw_spin_lock_flags() code, because lockdep assumes
* that interrupts are not re-enabled during lock-acquire:
*/
#ifdef CONFIG_LOCKDEP
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
#else
do_raw_spin_lock_flags(lock, &flags);
#endif
return flags;
}
这个问题从错误的断言开始:
On an SMP machine we must use spin_lock_irqsave and not spin_lock_irq from interrupt context.
这些都不应该在中断上下文、SMP 或 UP 上使用。也就是说,spin_lock_irqsave()
可以在中断上下文中使用,因为它更通用(它可以在中断和正常上下文中使用),但是你应该在spin_lock()
中断上下文中使用,spin_lock_irq()
或者spin_lock_irqsave()
从正常上下文中使用。在中断上下文中使用spin_lock_irq()
几乎总是错误的事情,即这个 SMP 或 UP。它可能会起作用,因为大多数中断处理程序在本地启用 IRQ 的情况下运行,但您不应该尝试这样做。
更新:由于有些人误解了这个答案,让我澄清一下,它只解释了什么是中断上下文锁定,什么不是中断上下文锁定。这里没有声明spin_lock()
只能
在中断上下文中使用。它也可以在进程上下文中使用,例如,如果不需要锁定中断上下文。