Locking in Linux Kernel
spinlock 和 mutex 是在 linux kernel 中广泛地使用地两种锁,这两种锁可以覆盖内核中大部分同步需求。
spinlock 即简单地自旋锁,如果暂时获取不到锁,则会一直重试(忙等待);而 mutex 则在获取不到锁地时候主动放弃 CPU(进行 sleep)。虽然 spinlock 很简单,但是其上锁/解锁地接口就有 spin_lock_lock/unlock
, spin_lock_irq/unlock_irq
, spin_lock_bh/unlock_bh
, spin_lock_irqsave/unlock_irqrestore
等,本文即试图总结应该如何正确地选择这些接口。如果读者对 kernel 中四种上下文还不熟悉,可以参考这篇文章。
Among User Contexts
如果临界资源只在不同地 user context 之间共享,那么此时使用 spinlock 的所有接口都是可以的(当然要配对使用),也可以使用 mutex 使得在无法立即获取锁的时候让出 CPU。
Between User Context and Softirq Context
如果临界资源在 user context 和 softirq context 之间共享,那么此时需要使用 spin_lock_bh/unlock_bh
来上锁/解锁。 spin_lock_bh
将先禁用当前 CPU 的 softirq 抢占,再尝试获取锁,而 spin_unlock_bh
则相反。
这样做的原因是 softirq 可以打断 user context,如果此时 user context 已经获取了锁,那么 softirq 再尝试获取锁时就会永远保持在忙等待,而由于 spinlock 是不会让出 CPU 的,这将导致已经拿到锁的 user context 再也没有机会获得 CPU,也就没法释放锁,从而导致了死锁。
Timer and Tasklet
由于 Timer 和 Tasklet 都是基于 softirq 来实现的,因此当它们与 user context 共享资源时同样需要使用 spin_lock_bh/unlock_bh
。
需要注意的是 hrtimer 默认运行在 hardware interrupt 上下文,因此如果需要进行相关的同步操作请参考下文硬中断相关。还可以通过指定 hrtimer 的 HRTIMER_MODE_SOFT
选项来让它运行在软中断上下文中,这样就可以使用本小节的方式进行同步了。
Between User Context and Hardware Interrupt Context
如果临界资源需要在 user context 和 hardware interrupt 之间共享,那么此时需要使用 spin_lock_irq/unlock_irq
。与软中断类似, spin_lock_irq
会先禁用当前 CPU 上的硬中断,再尝试获取锁。原因也与软中断类似,是为了避免硬中断侵入当前 user context 时可能产生的死锁。
Among Softirqs
由于同一个软中断可能同时运行在不同的 CPU 上,在这种情况下,即使是与自己共享数据也同样需要加锁(更高效的办法是使用 per-CPU data)。无论是 softirq 与自己共享数据,还是不同的 softirq 之间共享数据,只需要使用 spin_lock/unlock
即可。
Timer and Tasklet
如果是 timer 与 tasklet,或者 timer 与另一个 timer,或者 tasklet 与另一个 tasklet 之间共享数据,那么也使用 spin_lock/unlock
即可。
而虽然二者都基于 softirq 实现,但是 kernel 会保证同一个 timer 或 tasklet 不会同时运行在不同的 CPU 上,因此若它们与自己共享数据则不需要像 softirq 一样进行加锁。
Between Softirq and Hardware Interrupt Context
若软中断和硬中断之间共享数据,则需要使用 spin_lock_irq/unlock_irq
。原因与前文类似,也是为了避免死锁的产生。注意这种情况下在硬中断中可以简单地使用 spin_lock/unlock
。
Among Hardware Interrupt Contexts
此时需要使用 spin_lock_irqsave/unlock_irqrestore
来上锁/解锁。 spin_lock_irqsave
会先保存当前 CPU 上 irq 的启用状态,然后禁用当前 CPU 的 irq 抢占,最后尝试获取锁。
这一对函数还适用于上述的任意一种情况,所以当你不知道该选哪一对函数时,选择这一对总是没错的。
Cheat Sheet
下表整理了不同 context 之间需要加锁时的最低要求:
IRQ A | IRQ B | Softirq A | Softirq B | User A | User B | |
---|---|---|---|---|---|---|
IRQ A | No Need | |||||
IRQ B | *_irqsave | No Need | ||||
Softirq A | *_irq | *_irq | *_lock | |||
Softirq B | *_irq | *_irq | *_lock | *_lock | ||
User A | *_irq | *_irq | *_bh | *_bh | No Need | |
User B | *_irq | *_irq | *_bh | *_bh | mutex | No Need |
当然,使用 spin_lock_irqsave/unlock_irqrestore
总是不会错的。