linux spinlock的资料网上一大堆,但是每次查过之后,过段时间就忘记了,今天简单的总结下使用方法。
自旋锁最多可以被一个可执行线程持有,如果一个执行线程试图获取一个已经被持有的自旋锁,那么该线程就会一直进行忙循环--旋转--等待锁重新可用。要是锁未被持有,请求锁的执行线程便能立刻得到它,继续执行。在任意时间,自旋锁可以防止多于一个的执行线程同时进入临界区,同一个锁可以用在多个位置。
一个被争用的自旋锁使得请求它的线程在锁重新可用时自旋,这种行为是自旋锁的要点。所以自旋锁不应该被长时间持有。事实上,这点正是使用自旋锁的初衷:在短时间内进行轻量级加锁。
自旋锁方法:
使用的接口定义在<linux/spinlock.h>,自旋锁的基本使用形式如下:
DEFINE_SPINLOCK(mylock);
spin_lock(&mylock);
/* 临界区 */
spin_unlock(&mylock);
因为自旋锁在同一时刻至多被一个执行线程持有,所以一个时刻只能有一个线程位于临界区内,这就为多处理器提供了防止并发访问所需的机制。
linux内核实现的自旋锁是不可以递归调用的,这点不同于自旋锁在其他操作系统中的实现。
自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为他们会导致睡眠)。在中断处理程序中使用自旋锁时,一定要在获取锁之前,禁止本地中断(在当前处理器上的中断请求),否则中断处理程序会打断正持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁,这样一来,中断处理程序就会自旋,等待该锁重新可用,但是锁的持有者在这个中断处理程序执行完毕之前不可能运行,所以会导致死锁。注意:需要关闭的只是当前处理器上的中断,如果中断发生在不同的处理器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同的处理器上)最终释放锁。
内核提供的禁止中断同时请求锁的接口如下:
DEFINE_SPINLOCK(mylock);
unsigned long flags;
spin_lock_irqsave(&mylock, flags);
/* 临界区 */
spin_lock_irqrestore(&mylock, flags);
函数spin_lock_irqsave保存中断的当前状态,并禁止本地中断,然后去获取指定的锁。spin_lock_irqrestore对指定的锁解锁,然后让中断恢复到加锁前的状态,所以即使中断最初是被禁止的,代码也不会错误的去激活它们,相反,会继续让它们禁止。
如果能确定中断在加锁前是激活的,那就不需要在解锁后恢复中断以前的状态了,可以无条件的在解锁时激活中断,这时使用spin_lock_irq和spin_unlock_irq会更好些。
使用自旋锁的方法:
1 定义自旋锁变量 spinlock_t testlock;
2 初始化锁 spin_lock_init(&testlock);
以上两步也可以通过DEFINE_SPINLOCK(testlock)来实现
3 获取锁 spin_lock/spin_lock_irq/spin_lock_irqsave
4 释放锁 spin_unlock/spin_unlock_irq/spin_unlock_restore
几个函数的区别,参考了下网络资料,以下资料是从别人的博客中拷贝来的,谢谢该博客主人的分享:
void spin_lock(spinlock_t *lock);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
1、spin_lock与spin_lock_irq区别
在Linux内核中何时使用spin_lock,何时使用spin_lock_irqsave很容易混淆。首先看一下代码是如何实现的。
spin_lock的调用关系
spin_lock
|
+ -----> raw_spin_lock
|
+------> _raw_spin_lock
|
+--------> __raw_spin_lock
[cpp] view plaincopy
- 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的调用关系
spin_lock_irq
|
+-------> raw_spin_lock_irq
|
+---------> _raw_spin_lock_irq
|
+------------> __raw_spin_lock_irq
[cpp] view plaincopy
- 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);
- }
可以看出来他们两者只有一个差别:是否调用local_irq_disable()函数, 即是否禁止本地中断。
在任何情况下使用spin_lock_irq都是安全的。因为它既禁止本地中断,又禁止内核抢占。
spin_lock比spin_lock_irq速度快,但是它并不是任何情况下都是安全的。
举个例子:进程A中调用了spin_lock(&lock)然后进入临界区,此时来了一个中断(interrupt),
该中断也运行在和进程A相同的CPU上,并且在该中断处理程序中恰巧也会spin_lock(&lock)
试图获取同一个锁。由于是在同一个CPU上被中断,进程A会被设置为TASK_INTERRUPT状态,
中断处理程序无法获得锁,会不停的忙等,由于进程A被设置为中断状态,schedule()进程调度就
无法再调度进程A运行,这样就导致了死锁!
但是如果该中断处理程序运行在不同的CPU上就不会触发死锁。 因为在不同的CPU上出现中断不会导致
进程A的状态被设为TASK_INTERRUPT,只是换出。当中断处理程序忙等被换出后,进程A还是有机会
获得CPU,执行并退出临界区。
所以在使用spin_lock时要明确知道该锁不会在中断处理程序中使用。
2、spin_lock_irq与spin_lock_irqsave区别
spin_lock_irqsave在进入临界区前,保存当前中断寄存器flag状态,关中断,进入临界区,在退出临界区时,把保存的中断状态写回到中断寄存器。
spin_lock_irq在进入临界区前不保存中断状态,关中断,进入临界区,在退出临界区时,开中断。
spin_lock_irqsave锁返回时,中断状态不会被改变,调用spin_lock_irqsave前是开中断返回就开中断。
spin_lock_irq锁返回时,永远都是开中断,即使spin_lock_irq前是关中断