Spinlock 代码及故障模型分析

代码流程

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 是一个资源,为两者所争夺。实线代表存在实际代码执行的流程,虚线则代表仅逻辑上的流程。

case1

  • 在多核系统上:不会产生死锁1,仅性能降低,延迟增大。
  • 在开抢占的物理单核系统上:会产生死锁。

基于同样的原因,在使用 spinlock 的时候需要关抢占。不关抢占地使用 spinlock 的故障模型与上述类似,只需将上述的“睡眠”改为“被抢占”即可。

二、在中断上下文使用了错误的 spin_lock() 类型

case2

若一个 spinlock 可能同时在进程上下文和中断上下文中被使用,使用普通的 spin_lock/unlock() 可能导致死锁。此时应该使用 spin_lock_bhspin_lock_irqspin_lock_irqsave 等其他 API。这些 API 除了关抢占,还会关中断,以此来避免上述故障的发生。

参考资料


  1. 在无法动态调整优先级的调度算法上,还是可能死锁的。若进程 B 优先级高于 A,若 A 由于优先级低而永远无法得到调度,则产生死锁。 ↩︎


Linux 脚本执行:由一个补丁分析说起
用于内核测试的内核模块