什么是自旋锁--spinlock

本文详细介绍了自旋锁的概念、应用场景及API使用方法。自旋锁主要用于保护短时间内访问的共享资源,适用于中断上下文,可避免睡眠,提高系统响应速度。

原文地址:https://blog.csdn.net/jeffasd/article/details/51321743

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

  由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
  信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用(_trylock的变种能够在中断上下文使用),而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。
  如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共巷资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。
  自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。
  跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。
  无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。
  自旋锁的API有:
spin_lock_init(x)
  该宏用于初始化自旋锁x。自旋锁在真正使用前必须先初始化。该宏用于动态初始化。
DEFINE_SPINLOCK(x)
  该宏声明一个自旋锁x并初始化它。该宏在2.6.11中第一次被定义,在先前的内核中并没有该宏。
SPIN_LOCK_UNLOCKED
  该宏用于静态初始化一个自旋锁。
DEFINE_SPINLOCK(x)等同于spinlock_t x = SPIN_LOCK_UNLOCKED spin_is_locked(x)
  该宏用于判断自旋锁x是否已经被某执行单元保持(即被锁),如果是,返回真,否则返回假。
spin_unlock_wait(x)
  该宏用于等待自旋锁x变得没有被任何执行单元保持,如果没有任何执行单元保持该自旋锁,该宏立即返回,否则将循环在那里,直到该自旋锁被保持者释放。
spin_trylock(lock)
  该宏尽力获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则不能立即获得锁,立即返回假。它不会自旋等待lock被释放。
spin_lock(lock)
  该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放,这时,它获得锁并返回。总之,只有它获得锁才返回。
spin_lock_irqsave(lock, flags)
  该宏获得自旋锁的同时把标志寄存器的值保存到变量flags中并失效本地中断。
spin_lock_irq(lock)
  该宏类似于spin_lock_irqsave,只是该宏不保存标志寄存器的值。
spin_lock_bh(lock)
  该宏在得到自旋锁的同时失效本地软中断。
spin_unlock(lock)
  该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。如果spin_trylock返回假,表明没有获得自旋锁,因此不必使用spin_unlock释放。
spin_unlock_irqrestore(lock, flags)
  该宏释放自旋锁lock的同时,也恢复标志寄存器的值为变量flags保存的值。它与spin_lock_irqsave配对使用。
spin_unlock_irq(lock)
  该宏释放自旋锁lock的同时,也使能本地中断。它与spin_lock_irq配对应用。
spin_unlock_bh(lock)
  该宏释放自旋锁lock的同时,也使能本地的软中断。它与spin_lock_bh配对使用。
spin_trylock_irqsave(lock, flags)
  该宏如果获得自旋锁lock,它也将保存标志寄存器的值到变量flags中,并且失效本地中断,如果没有获得锁,它什么也不做。
  因此如果能够立即获得锁,它等同于spin_lock_irqsave,如果不能获得锁,它等同于spin_trylock。如果该宏获得自旋锁lock,那需要使用spin_unlock_irqrestore来释放。
spin_trylock_irq(lock)
  该宏类似于spin_trylock_irqsave,只是该宏不保存标志寄存器。如果该宏获得自旋锁lock,需要使用spin_unlock_irq来释放。
spin_trylock_bh(lock)
  该宏如果获得了自旋锁,它也将失效本地软中断。如果得不到锁,它什么也不做。因此,如果得到了锁,它等同于spin_lock_bh,如果得不到锁,它等同于spin_trylock。如果该宏得到了自旋锁,需要使用spin_unlock_bh来释放。
spin_can_lock(lock)
  该宏用于判断自旋锁lock是否能够被锁,它实际是spin_is_locked取反。如果lock没有被锁,它返回真,否则,返回假。该宏在2.6.11中第一次被定义,在先前的内核中并没有该宏。
  获得自旋锁和释放自旋锁有好几个版本,因此让读者知道在什么样的情况下使用什么版本的获得和释放锁的宏是非常必要的。
  如果被保护的共享资源只在进程上下文访问和软中断上下文访问,那么当在进程上下文访问共享资源时,可能被软中断打断,从而可能进入软中断上下文来对被保护的共享资源访问,因此对于这种情况,对共享资源的访问必须使用spin_lock_bh和spin_unlock_bh来保护。
  当然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它们失效了本地硬中断,失效硬中断隐式地也失效了软中断。但是使用spin_lock_bh和spin_unlock_bh是最恰当的,它比其他两个快。
  如果被保护的共享资源只在进程上下文和tasklet或timer上下文访问,那么应该使用与上面情况相同的获得和释放锁的宏,因为tasklet和timer是用软中断实现的。
  如果被保护的共享资源只在一个tasklet或timer上下文访问,那么不需要任何自旋锁保护,因为同一个tasklet或timer只能在一个CPU上运行,即使是在SMP环境下也是如此。实际上tasklet在调用tasklet_schedule标记其需要被调度时已经把该tasklet绑定到当前CPU,因此同一个tasklet决不可能同时在其他CPU上运行。
  timer也是在其被使用add_timer添加到timer队列中时已经被帮定到当前CPU,所以同一个timer绝不可能运行在其他CPU上。当然同一个tasklet有两个实例同时运行在同一个CPU就更不可能了。
  如果被保护的共享资源只在两个或多个tasklet或timer上下文访问,那么对共享资源的访问仅需要用spin_lock和spin_unlock来保护,不必使用_bh版本,因为当tasklet或timer运行时,不可能有其他tasklet或timer在当前CPU上运行。
 如果被保护的共享资源只在一个软中断(tasklet和timer除外)上下文访问,那么这个共享资源需要用spin_lock和spin_unlock来保护,因为同样的软中断可以同时在不同的CPU上运行。
  如果被保护的共享资源在两个或多个软中断上下文访问,那么这个共享资源当然更需要用spin_lock和spin_unlock来保护,不同的软中断能够同时在不同的CPU上运行。
  如果被保护的共享资源在软中断(包括tasklet和timer)或进程上下文和硬中断上下文访问,那么在软中断或进程上下文访问期间,可能被硬中断打断,从而进入硬中断上下文对共享资源进行访问,因此,在进程或软中断上下文需要使用spin_lock_irq和spin_unlock_irq来保护对共享资源的访问。
  而在中断处理句柄中使用什么版本,需依情况而定,如果只有一个中断处理句柄访问该共享资源,那么在中断处理句柄中仅需要spin_lock和spin_unlock来保护对共享资源的访问就可以了。
  因为在执行中断处理句柄期间,不可能被同一CPU上的软中断或进程打断。但是如果有不同的中断处理句柄访问该共享资源,那么需要在中断处理句柄中使用spin_lock_irq和spin_unlock_irq来保护对共享资源的访问。
  在使用spin_lock_irq和spin_unlock_irq的情况下,完全可以用spin_lock_irqsave和spin_unlock_irqrestore取代,那具体应该使用哪一个也需要依情况而定,如果可以确信在对共享资源访问前中断是使能的,那么使用spin_lock_irq更好一些。
  因为它比spin_lock_irqsave要快一些,但是如果你不能确定是否中断使能,那么使用spin_lock_irqsave和spin_unlock_irqrestore更好,因为它将恢复访问共享资源前的中断标志而不是直接使能中断。
  当然,有些情况下需要在访问共享资源时必须中断失效,而访问完后必须中断使能,这样的情形使用spin_lock_irq和spin_unlock_irq最好。

  spin_lock用于阻止在不同CPU上的执行单元对共享资源的同时访问以及不同进程上下文互相抢占导致的对共享资源的非同步访问,而中断失效和软中断失效却是为了阻止在同一CPU上软中断或中断对共享资源的非同步访问。

### 自旋锁的概念 自旋锁是一种轻量级的锁定机制,主要用于多处理器系统中的并发控制。当一个线程试图获取已被其他线程持有的自旋锁时,该线程不会进入睡眠状态,而是不断循环检查锁的状态,直到锁被释放为止[^1]。 这种行为被称为“忙等待”,因为线程在等待期间仍然占用 CPU 时间。尽管这看似浪费资源,但在某些情况下(如锁持有时间非常短),这种方法可以减少上下文切换开销并提高性能。 --- ### 自旋锁的工作原理 自旋锁的核心思想是通过原子操作来管理对共享资源的访问权限。以下是其基本工作机制: 1. **初始化阶段**:创建一个标志位表示锁的状态(通常是一个布尔值或整数)。 2. **加锁过程**:尝试修改标志位以表明自己已获得锁。如果失败,则重复尝试直至成功。 3. **解锁过程**:将标志位置回初始状态,允许其他线程竞争锁。 下面展示了一个简单的伪代码实现: ```c typedef struct { int flag; } spinlock_t; void spin_lock(spinlock_t *lock) { while (__sync_lock_test_and_set(&lock->flag, 1)) { // 尝试设置锁 cpu_relax(); // 提示编译器优化忙等待逻辑 } } void spin_unlock(spinlock_t *lock) { lock->flag = 0; // 解锁 } ``` 上述代码利用了 `__sync_lock_test_and_set` 这一原子指令完成无干扰的操作,并通过轮询检测锁是否可用[^2]。 --- ### 自旋锁的应用场景 #### 1. 锁争用较少的情况 由于自旋锁会让线程保持忙碌状态而不休眠,因此它适合于那些预期锁争用较低、锁持有时间极短的情形。例如,在高频率但低延迟需求的任务中使用效果较好。 #### 2. 实时性和响应速度优先的场合 对于实时性强或者不允许长时间阻塞的应用程序来说,采用自旋锁能够快速恢复执行流,从而满足严格的时限约束条件[^3]。 #### 3. 不同类型的中断处理 根据具体应用场景的不同,可以选择不同版本的自旋锁函数: - `spin_lock()`:仅用于普通内核线程间的同步; - `spin_lock_bh()`:阻止软中断的同时保护临界区数据结构; - `spin_lock_irq()` 和 `spin_lock_irqsave()`:分别针对硬中断以及伴随保存/恢复中断状态的需求设计。 需要注意的是,在单处理器环境下,频繁使用自旋锁可能导致不必要的 CPU 资源消耗;而在多核架构下则能发挥更好的效能优势。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值