Linux 设备驱动程序:重温持有锁时休眠

Linux Device Drivers: Sleeping whiile Holding Lock Revisited

我一直被教导在内核代码中持有自旋锁时睡觉是不可以的。原因如下:

  1. 线程 A 获取锁,做一些工作,然后调用进入休眠状态的内核函数,释放 CPU。
  2. 线程 B 现在 运行s,它试图获取锁,但不能,因为它被线程 A 持有。
  3. 这是一个僵局。线程A无法唤醒,因为线程B一直在自旋,线程B无法取得任何进展,因为它无法获得锁。

我正在维护一个使用大量锁的驱动程序,并且在一些锁定的部分内部是显而易见的事情,例如内存分配,copy_to_user(),等等

但是,我并不完全相信我手上有错误。使用上述场景,线程 A 是用户上下文(即在 read() 的实现内部),而线程 B 是中断上下文(在 ISR 内部)。锁是通过 spin_lock_irqsave() 锁定的。结果,当线程 A 持有锁时,线程 B 不能 运行,从而不可能发生死锁。

我还考虑了以下几点:

  1. 线程 A = 中断上下文。这里线程 A 无法休眠(也没有),所以我们永远不会进入死锁状态。
  2. 线程 A 和 B 都是用户上下文(即并发调用 read() )。由于存在其他机制,这不会发生。

有什么我遗漏的吗?在我上面描述的情况下,拿着锁睡觉有什么真正的危险吗?

问题不成立

Using the above scenario, Thread A is user context (namely inside the implementation of read()) while Thread B is the interrupt context (inside the ISR). The lock is locked via spin_lock_irqsave(). As a result, Thread B cannot run while Thread A holds the lock, making the deadlock not possible.

如果你在 copy_to_user & 朋友之间持有自旋锁,带有调试功能的内核会警告你你做错了。如果需要让线程进入睡眠状态,它就会进入睡眠状态。然后你回到第一点。

I've always been taught that sleeping while holding a spinlock in kernel code is a no-no.

你混淆了自旋锁禁用中断(原子上下文,IRQ)。

  1. 只是睡觉,同时拥有自旋锁(通过spin_lock获得)危害perfomance,因为另一个想要锁定自旋锁的线程在忙等待中浪费了时间。但除此之外系统没问题。

  2. 但是休眠禁用中断意味着CPU核心死了:它不执行任何操作,也不对来自其他核心和外部世界的中断做出反应。

    如果您在 spin_lock_irqsave 之后调用 copy_from_user,就会发生这种情况:第一个操作禁用中断,第二个可能会休眠。


通常禁用中断伴随着自旋锁(通过spin_lock_irqsave或类似的方式)。否则,如果 IRQ 线程将尝试获取相同的自旋锁,将观察到 死锁:所有者线程无法继续,因为它被抢占,IRQ 线程无法继续,因为它等待自旋锁。

如果没有 IRQ 线程(或由于其他原因禁用中断的线程)锁定给定的自旋锁,则不需要禁用中断来获取自旋锁。当不需要禁用中断时,可以使用spin_lock代替spin_lock_irqsave。但是 非常推荐 将非 IRQ 自旋锁替换为 互斥锁