spin_lock与spin_lock_irq与spin_lock_irqsave

spin_lock与spin_lock_irq两者差别在于是否调用local_irq_disable()函数, 即是否禁止本地中断。
spin_lock比spin_lock_irq速度快,但是它并不是任何情况下都是安全的。在任何情况下使用spin_lock_irq都是安全的。因为它既禁止本地中断,又禁止内核抢占。
spin_lock_irqsave禁止内核抢占,关闭中断,保存中断状态寄存器的标志位,spin_lock_irqsave在锁返回时,进入中断之前开的之后也是开的,之前关,之后也是关的,spin_lock_irq则不管之前是否开关,返回的时候都是开的。

//spin_lock  
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

//spin_lock_irq
static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
local_irq_disable();
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

抢占,中断,多个cpu情况下访问共享资源

单个cpu非抢占

自旋锁的目的如果在系统不支持内核抢占时,自旋锁的实现也是空的,因为单核只有一个线程在执行,不会有内核抢占,从而资源也不会被其他线程访问到。


单个cpu抢占

使用spin_lock会关闭内核抢占。
发生在线程与线程之间,如果不关闭内核抢占,A线程与B线程,A线程持有自旋锁,此时由于B线程优先级高,B线程被执行,也试图获取自旋锁,此时会造成死锁。所以这个时候,在进程A中使用spin_lock,获取lock之后,关闭内核抢占,就不会出现死锁的情况。


单个cpu有中断

使用spin_lock_irq不但会关闭内核抢占也禁止中断。
发生在线程与中断之间,如果不关闭中断,A线程与B中断处理程序,A线程持有自旋锁,此时来了一个中断,中断被执行,也试图获取自旋锁时造成死锁。所以这个时候,在进程A中使用spin_lock_irq,获取lock之后,不但关闭内核抢占,也禁止中断,就不会出现死锁的情况。


多个cpu

A线程运行在cpu0,B线程运行在cpu1,C中断运行在cpu1
A线程持有自旋锁,此时cpu1上来中断,C中断被执行,也试图获取自旋锁,但是是获取不到的,得等到A线程释放自旋锁之后才能获取自旋锁,在不同的cpu上线程与中断访问共享资源,此时用spin_lock就行了。
A线程运行在cpu0,B线程运行在cpu1,C中断运行在cpu0
这种就和单个cpu有中断情况是一样的,在同一个cpu上线程与中断之间访问共享资源,是需要用spin_lock_irq的。


自旋锁使用注意

自旋锁一直占用CPU,他在未获得锁的情况下,一直运行
在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。
因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。


参考文章

  1. ​Linux内核同步机制之(四):spin lock​
  2. ​​ 自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析​​