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 AIRQ BSoftirq ASoftirq BUser AUser B
IRQ ANo Need
IRQ B*_irqsaveNo Need
Softirq A*_irq*_irq*_lock
Softirq B*_irq*_irq*_lock*_lock
User A*_irq*_irq*_bh*_bhNo Need
User B*_irq*_irq*_bh*_bhmutexNo Need

当然,使用 spin_lock_irqsave/unlock_irqrestore 总是不会错的。