【发布时间】:2011-02-03 07:32:58
【问题描述】:
在 SMP 机器上,我们必须在中断上下文中使用 spin_lock_irqsave 而不是 spin_lock_irq。
我们为什么要保存标志(包含 IF)?
是否有另一个中断程序可以中断我们?
【问题讨论】:
标签: linux-kernel spinlock
在 SMP 机器上,我们必须在中断上下文中使用 spin_lock_irqsave 而不是 spin_lock_irq。
我们为什么要保存标志(包含 IF)?
是否有另一个中断程序可以中断我们?
【问题讨论】:
标签: linux-kernel spinlock
spin_lock_irqsave基本上是用来保存在获取自旋锁之前的中断状态,这是因为自旋锁在中断上下文中获取锁时禁用中断,而在解锁时重新启用它。中断状态被保存,以便它应该再次恢复中断。
例子:
spin_lock_irq 将禁用中断 x 并获取锁spin_unlock_irq 将启用中断 x。所以在释放锁后的第三步中,我们将启用中断 x,该中断在获得锁之前被禁用。
因此,只有当您确定没有禁用中断时,您才应该使用spin_lock_irq,否则您应该始终使用spin_lock_irqsave。
【讨论】:
如果在您的代码开始锁定之前中断已被禁用,当您调用spin_unlock_irq 时,您将以可能不需要的方式强制重新启用中断。相反,如果您还将当前中断启用状态保存在flags 到spin_lock_irqsave 中,尝试在释放锁定后使用相同的flags 重新启用中断,该函数将只是恢复以前的状态(因此不一定启用中断)。
以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
spin_lock_irq( 不带 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...
【讨论】:
spin_lock_irq 是为了表明在不应该启用 IRQ 时启用了 IRQ。答案没有错;只是不太清楚。
spin_lock_irqsave 是一个宏,而不是一个函数,这就是它可以更改“passed”flags 变量的原因。这让我感到困惑,直到我真正在内核代码中查找它。
除了spin_lock_irq 之外还需要spin_lock_irqsave 与除了local_irq_disable 之外还需要local_irq_save(flags) 的原因非常相似。下面是 Robert Love 的 Linux Kernel Development Second Edition 对这一要求的一个很好的解释。
如果中断被中断,local_irq_disable() 例程是危险的 在调用之前已经禁用。对应的调用 local_irq_enable() 无条件启用中断,尽管 他们一开始就离开的事实。相反,需要一种机制 将中断恢复到以前的状态。这是一个普遍的担忧 因为内核中给定的代码路径可以通过 和 不启用中断,具体取决于调用链。例如, 想象一下前面的代码 sn-p 是一个更大函数的一部分。 想象一下,这个函数被另外两个函数调用,一个 禁用中断,一个不禁用。因为它正在成为 随着内核规模和复杂性的增长,了解所有代码变得更加困难 通向函数的路径,保存状态更安全 禁用它之前的中断系统。然后,当你准备好 重新启用中断,您只需将它们恢复到原始状态:
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 中断必须发生在同一个函数中。
前面的所有函数都可以从中断和 进程上下文。
【讨论】:
阅读 Why kernel code/thread executing in interrupt context cannot sleep? 链接到 Robert Loves article,我读到了这个:
一些中断处理程序(已知 Linux 作为快速中断处理程序)运行 与本地的所有中断 处理器禁用。这是为了 确保中断处理程序运行 不间断,尽快 可能的。更何况,所有的中断 处理程序以其当前运行 中断线全部禁用 处理器。这确保了两个 相同的中断处理程序 中断线不运行 同时。它还可以防止设备 司机作家不必处理 递归中断,这很复杂 编程。
【讨论】:
下面是linux kernel 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() 应该
仅在中断上下文中使用。它可以在一个过程中使用
上下文也是,例如如果不需要锁定中断
上下文。
【讨论】: