非抢占式 Linux 内核中的 spin_lock

4

我看到在一个只有1个CPU和非抢占式Linux内核(2.6.x)的系统上,spin_lock调用等同于一个空调用,并因此以这种方式实现。

我无法理解这一点:难道它不应该等同于对互斥锁进行休眠吗?即使在非抢占式内核中,例如中断处理程序仍可能被执行,或者我可能调用会使原始线程进入睡眠状态的函数。因此,一个空的spin_lock调用并不是“安全”的,就像如果它被实现为互斥锁一样。

有些地方我没有理解吗?

4个回答

6

如果在非可抢占内核上使用spin_lock()来保护数据免受中断处理程序的干扰,你将会出现死锁(在单处理器机器上)。

如果中断处理程序在其他内核代码持有锁时运行,它将永远旋转,因为常规内核代码没有办法恢复并释放锁。

只有当锁持有者可以始终运行到完成时,才能使用自旋锁。

对于可能被中断处理程序所需的锁,解决方案是使用spin_lock_irqsave(),它在持有自旋锁时禁用中断。在1个 CPU 上,没有中断处理程序可以运行,因此不会出现死锁。在SMP上,中断处理程序可能会在另一个 CPU 上开始自旋,但由于持有锁的 CPU 不能被中断,所以锁最终将被释放。


6
回答你问题的两个方面:
即使在非抢占式内核上,中断处理程序仍可能被执行,例如...。
spin_lock() 不应该保护中断处理程序 - 只保护用户上下文内核代码。 spin_lock_irqsave() 是中断禁用版本,在非抢占式单处理器上这不是一个空操作。
...或者我可能调用会导致原始线程睡眠的函数。
持有自旋锁时不允许睡眠,这是“原子情况下进行调度”的错误。如果要睡眠,必须使用互斥锁(同样,在非抢占式单处理器上这些并不是空操作)。

5
引用自 Jonathan Corbet、Alessandro Rubini 和 Greg Kroah-Hartman 的《Linux 设备驱动程序》:
如果一个非抢先式单处理器系统在锁定状态下进入自旋,那么它将永远自旋;没有其他线程能够获得 CPU 来释放锁(因为它不能放弃)。由于这个原因,在没有启用抢占的单处理器系统上,自旋锁操作被优化为不执行任何操作,除了改变 IRQ 屏蔽状态的操作(在 Linux 中,那就是 spin_lock_irqsave())。由于抢占,即使您从未预期过您的代码会在 SMP 系统上运行,您仍然需要实现适当的锁定。
如果您对可以由硬件或软件中断上下文中运行的代码获取的自旋锁感兴趣,那么您必须使用一种禁用中断的 spin_lock_*。如果不这样做,那么只要在您进入临界区时出现中断,系统就会死锁。

实际上,对于抢占式单处理器系统而言,情况是相同的,因为 spin_lock 调用会禁用抢占。 - Dipstick
如果一个非抢占式单处理器系统在锁上进入了自旋状态,那么它将永远无法离开该状态。这是由于在非抢占式系统中,只有当前运行的进程可以释放锁,但是如果该进程一直处于自旋状态,则其他等待该锁的进程将永远无法获得该锁。因此,非抢占式系统必须采用某种机制来避免自旋锁问题。 - Macmade

1

根据定义,如果您使用的是非抢占式内核,则不会被抢占。如果您自己进行多任务处理,那不是内核的问题,而是您的问题。中断处理程序仍然可能被执行,但它们不会引起上下文切换。


1
还有,这是否意味着我不能在我的自旋锁关键部分进行可能会阻塞的调用?(像 kmalloc 或 printk?) - Emiliano
除非自旋锁的所有其他用户在获取锁之前禁用 IRQ(请参见 Eric 的回答),否则 IRQ 不应使用自旋锁。如果允许您所描述的情况,则会导致死锁(处理器在 IRQ 处理程序中旋转,但是在处理器运行另一段代码之前无法释放锁)。 - Thomas M. DuBuisson
@happy_emi 回答你的另一个问题,当持有自旋锁时,不应进行可能会阻塞、休眠或重新调度的调用,因为这也可能导致死锁。 - Eric Seppanen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接