前言:
-
一、自旋锁spinlock的由来
-
二、自旋锁的目的
-
三、自旋锁需要做的工作
有些人会以为自旋锁的自旋检测可以用for实现,这种想法“Too young,
(1)你如何保证在SMP下其他处理器不会同时访问同一个的标志呢?(也就是标志的独占访问)
(2)必须保证每个处理器都不会去读取高速缓存而是真正的内存中的标志(可以实现,编程上可以用volitale)
-
中断控制(仅在中断代码可能访问临界区时需要)
-
抢占控制(仅存在于可抢占内核中需要)
-
自旋锁标志控制
(仅SMP系统需要)
-
到这里其实就可以解释为什么我开始的实验现象和预想的完全不同了: -
由于UP系统(在不配置CONFIG_DEBUG_SPINLOCK的情况下),根本就没有自旋锁控制的部分,多次获得自旋锁是可能的(这种编程本来就是错误的,只是我想看错误的现象而已)。
- 对于其中的一点疑惑:
- 1、在有禁用中断的版本中,既然已经禁用了中断,在本处理器上就不会被打断,禁用抢占是否多余?
- (1)禁用了中断可以避免因为中断引起的抢占调度,但是如果在自旋锁保护的临界区中存在 preempt_disable();和 preempt_enable();对。这样在preempt_enable();就会引发抢占调度。
- (2)避免SMP系统中别的处理器执行调度程序使得本处理器的进程会被调度出去。?????
- 对于这个问题我不是很确定,还有深入研究调度系统后才会有准确的答案。
-
五、自旋锁变体的使用规则
- 不会在任何中断例程中操作临界区:
-
- static inline void spin_lock(spinlock_t
*lock)
-
-
static inline void spin_unlock(spinlock_t *lock)
-
- 如果在软件中断中操作临界区:
-
-
static inline void spin_lock_bh(spinlock_t *lock)
-
-
static inline void spin_unlock_bh(spinlock_t *lock)
-
- bh代表bottom half,也就是中断中的底半部,因内核中断的底半部一般通过软件中断(tasklet等)来处理而得名。
-
- 如果在硬件中断中操作临界区:
-
- static inline void spin_lock_irq(spinlock_t
*lock)
-
- static inline void spin_unlock_irq(spinlock_t
*lock)
-
- 如果在控制硬件中断的时候需要同时保存中断状态:
-
- spin_lock_irqsave(lock,
flags)
-
- static inline void spin_unlock_irqrestore(spinlock_t
*lock, unsigned long flags)
-
获得自旋锁和释放自旋锁有好几个版本,因此让读者知道在什么样的情况下使用什么版本的获得和释放锁的宏是非常必要的。 如果被保护的共享资源 只在进程上下文访问和软中断(包括tasklet、timer)上下文访问,那么当在进程上下文访问共享资源时,可能被软中断打断,从而可能进入软中断上下文来对被保护的共享资源访问,因此对于这种情况,对共享资源的访问必须 使用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上下文访问,那么对共享资源的访问 仅需要用spin_lock和spin_unlock来保护,不必使用_bh版本,因为当tasklet或timer运行时,不可能有其他tasklet或timer在当前CPU上运行。
如果被保护的共享资源 只在一个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来保护,因为同样的软中断可以同时在不同的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上软中断或中断对共享资源的非同步访问。
分析Linux中Spinlock在ARM及X86平台上的实现