代码流程
spinlock 上锁的代码流程:
1# include/linux/spinlock.h
2spin_lock
3 raw_spin_lock
4 # ifdef CONFIG_INLINE_SPIN_LOCK
5 # include/linux/spinlock_api_smp.h
6 # else
7 # kernel/locking/spinlock.c
8 _raw_spin_lock
9 # include/linux/spinlock_api_smp.h
10 __raw_spin_lock
11 preempt_disable()
12 spin_acquire(...)
可见,上锁时 spinlock 是先关抢占,再尝试获取锁的。换言之,进程在获取不到 spinlock 从而自旋时,抢占也是关着的。
spinlock 解锁的代码流程:
1# include/linux/spinlock.h
2spin_unlock
3 raw_spin_unlock
4 # ifndef CONFIG_UNINLINE_SPIN_UNLOCK
5 # include/linux/spinlock_api_smp.h
6 # else
7 # kernel/locking/spinlock.c
8 _raw_spin_unlock
9 # include/linux/spinlock_api_smp.h
10 __raw_spin_unlock
11 spin_release(...)
12 preempt_enable
相应地,解锁过程中是先释放锁,再开抢占。
spinlock 故障模型
一、在 spinlock 临界区内睡眠
如下图所示:存在 A、B 两个进程,两者为 Actors。Lock 是一个资源,为两者所争夺。实线代表存在实际代码执行的流程,虚线则代表仅逻辑上的流程。
- 在多核系统上:不会产生死锁1,仅性能降低,延迟增大。
- 在开抢占的物理单核系统上:会产生死锁。
基于同样的原因,在使用 spinlock 的时候需要关抢占。不关抢占地使用 spinlock 的故障模型与上述类似,只需将上述的“睡眠”改为“被抢占”即可。
二、在中断上下文使用了错误的 spin_lock()
类型
若一个 spinlock 可能同时在进程上下文和中断上下文中被使用,使用普通的 spin_lock/unlock()
可能导致死锁。此时应该使用 spin_lock_bh
、spin_lock_irq
和 spin_lock_irqsave
等其他 API。这些 API 除了关抢占,还会关中断,以此来避免上述故障的发生。
参考资料
-
5.5.2. Spinlocks and Atomic Context, LDD3
-
c - Why is “sleeping” not allowed while holding a spinlock? - Stack Overflow
-
NO_HZ: Reducing Scheduling-Clock Ticks — The Linux Kernel documentation
-
No NMI? No Problem! – Implementing Arm64 Pseudo-NMI | Kernel Recipes 2019 (kernel-recipes.org)
-
4. Exception types, Learn the architecture - AArch64 Exception model (arm.com)
-
Interrupt Handling, ULK3
-
在无法动态调整优先级的调度算法上,还是可能死锁的。若进程 B 优先级高于 A,若 A 由于优先级低而永远无法得到调度,则产生死锁。 ↩︎