Linux 设备驱动程序:重温持有锁时休眠
Linux Device Drivers: Sleeping whiile Holding Lock Revisited
我一直被教导在内核代码中持有自旋锁时睡觉是不可以的。原因如下:
- 线程 A 获取锁,做一些工作,然后调用进入休眠状态的内核函数,释放 CPU。
- 线程 B 现在 运行s,它试图获取锁,但不能,因为它被线程 A 持有。
- 这是一个僵局。线程A无法唤醒,因为线程B一直在自旋,线程B无法取得任何进展,因为它无法获得锁。
我正在维护一个使用大量锁的驱动程序,并且在一些锁定的部分内部是显而易见的事情,例如内存分配,copy_to_user()
,等等
但是,我并不完全相信我手上有错误。使用上述场景,线程 A 是用户上下文(即在 read()
的实现内部),而线程 B 是中断上下文(在 ISR 内部)。锁是通过 spin_lock_irqsave()
锁定的。结果,当线程 A 持有锁时,线程 B 不能 运行,从而不可能发生死锁。
我还考虑了以下几点:
- 线程 A = 中断上下文。这里线程 A 无法休眠(也没有),所以我们永远不会进入死锁状态。
- 线程 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)。
只是睡觉,同时拥有自旋锁(通过spin_lock
获得)危害perfomance,因为另一个想要锁定自旋锁的线程在忙等待中浪费了时间。但除此之外系统没问题。
但是休眠禁用中断意味着CPU核心死了:它不执行任何操作,也不对来自其他核心和外部世界的中断做出反应。
如果您在 spin_lock_irqsave
之后调用 copy_from_user
,就会发生这种情况:第一个操作禁用中断,第二个可能会休眠。
通常禁用中断伴随着自旋锁(通过spin_lock_irqsave
或类似的方式)。否则,如果 IRQ 线程将尝试获取相同的自旋锁,将观察到 死锁:所有者线程无法继续,因为它被抢占,IRQ 线程无法继续,因为它等待自旋锁。
如果没有 IRQ 线程(或由于其他原因禁用中断的线程)锁定给定的自旋锁,则不需要禁用中断来获取自旋锁。当不需要禁用中断时,可以使用spin_lock
代替spin_lock_irqsave
。但是 非常推荐 将非 IRQ 自旋锁替换为 互斥锁 。
我一直被教导在内核代码中持有自旋锁时睡觉是不可以的。原因如下:
- 线程 A 获取锁,做一些工作,然后调用进入休眠状态的内核函数,释放 CPU。
- 线程 B 现在 运行s,它试图获取锁,但不能,因为它被线程 A 持有。
- 这是一个僵局。线程A无法唤醒,因为线程B一直在自旋,线程B无法取得任何进展,因为它无法获得锁。
我正在维护一个使用大量锁的驱动程序,并且在一些锁定的部分内部是显而易见的事情,例如内存分配,copy_to_user()
,等等
但是,我并不完全相信我手上有错误。使用上述场景,线程 A 是用户上下文(即在 read()
的实现内部),而线程 B 是中断上下文(在 ISR 内部)。锁是通过 spin_lock_irqsave()
锁定的。结果,当线程 A 持有锁时,线程 B 不能 运行,从而不可能发生死锁。
我还考虑了以下几点:
- 线程 A = 中断上下文。这里线程 A 无法休眠(也没有),所以我们永远不会进入死锁状态。
- 线程 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)。
只是睡觉,同时拥有自旋锁(通过
spin_lock
获得)危害perfomance,因为另一个想要锁定自旋锁的线程在忙等待中浪费了时间。但除此之外系统没问题。但是休眠禁用中断意味着CPU核心死了:它不执行任何操作,也不对来自其他核心和外部世界的中断做出反应。
如果您在
spin_lock_irqsave
之后调用copy_from_user
,就会发生这种情况:第一个操作禁用中断,第二个可能会休眠。
通常禁用中断伴随着自旋锁(通过spin_lock_irqsave
或类似的方式)。否则,如果 IRQ 线程将尝试获取相同的自旋锁,将观察到 死锁:所有者线程无法继续,因为它被抢占,IRQ 线程无法继续,因为它等待自旋锁。
如果没有 IRQ 线程(或由于其他原因禁用中断的线程)锁定给定的自旋锁,则不需要禁用中断来获取自旋锁。当不需要禁用中断时,可以使用spin_lock
代替spin_lock_irqsave
。但是 非常推荐 将非 IRQ 自旋锁替换为 互斥锁 。