内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:
- 一个是原地等待
- 一个是挂起当前进程,调度其他进程执行(睡眠)
spin lock
是一种死等的机制,当前的执行 thread
会不断的重新尝试直到获取锁进入临界区。
spin lock
一次只能有一个 thread
获取锁并进入临界区,其他的 thread
都是在等待并不断的尝试获取锁。
由于 spin lock
死等这种特性,因此它使用在那些代码不是非常复杂的临界区,如果临界区执行时间太长,那么不断在临界区门口“死等”的那些 thread
会造成 CPU
浪费。
可以在中断上下文执行。由于不睡眠,因此 spin lock
可以在中断上下文中适用。
Linux
内核使用结构体 spinlock_t
表示自旋锁,定义在 linux/spinlock_types.h
文件中。结构体定义如下所示:
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
在使用自旋锁之前,肯定要先定义一个自旋锁变量,定义方法如下所示:
spinlock_t lock; //定义自旋锁
最基本的自旋锁 API
函数如下所示:
函数 | 描述 |
---|---|
DEFINE_SPINLOCK(spinlock_t lock) | 定义并初始化一个自选变量 |
int spin_lock_init(spinlock_t *lock) | 初始化自旋锁 |
void spin_lock(spinlock_t *lock) | 获取指定的自旋锁,也叫做加锁 |
void spin_unlock(spinlock_t *lock) | 释放指定的自旋锁 |
int spin_trylock(spinlock_t *lock) | 尝试获取指定的自旋锁,如果没有获取到就返回 0 |
int spin_is_locked(spinlock_t *lock) | 检查指定的自旋锁是否被获取,如果没有被获取就 返回非 0,否则返回 0 |
自旋锁 API
函数适用于 SMP
或支持抢占的单 CPU
下线程之间的并发访问,也就是用于线程与线程之间,被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的 API
函数,否则的话会可能会导致死锁现象的发生。
自旋锁会自动禁止抢占,也就说当线程 A
得到锁以后会暂时禁止内核抢占。如果线程 A
在持有锁期间进入了休眠状态,那么线程 A
会自动放弃 CPU
使用权。线程 B
开始运行,线程 B
也想要获取锁,但是此时锁被A线程持有,而且内核抢占还被禁止了!线程 B
无法被调度出去,那么线程 A
就无法运行,锁也就无法释放, 好了,死锁发生了!
如果中断也想访问共享资源,那该怎么办呢?首先可以肯定的是,中断里面可以使用自旋锁,但是在中断里面使用自旋锁的时候,在获取锁之前一定要先禁止本地中断(也就是本 CPU
中断,对于多核 SOC
来说会有多个 CPU
核),否则可能导致锁死现象的发生。
死锁的最好的解决方法就是获取锁之前关闭本地中断,Linux
内核提供了相应的 API
函数,如表下所示:
函数 | 描述 |
---|---|
void spin_lock_irq(spinlock_t *lock) | 禁止本地中断,并获取自旋锁。 |
void spin_unlock_irq(spinlock_t *lock) | 激活本地中断,并释放自旋锁。 |
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags) | 保存中断状态,禁止本地中断,并获取自旋锁。 |
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) | 将中断状态恢复到以前的状态,并且激活本地中断, 释放自旋锁。 |
使用 spin_lock_irq/spin_unlock_irq
的时候需要用户能够确定加锁之前的中断状态,但实际上内核很庞大,运行也是“千变万化”,我们是很难确定某个时刻的中断状态,因此不推荐使用 spin_lock_irq/spin_unlock_irq
。建议使用 spin_lock_irqsave/ spin_unlock_irqrestore
,因为这一组函数会保存中断状态,在释放锁的时候会恢复中断状态。
linux kernel
中提供了丰富的 bottom half
的机制,虽然同属中断上下文,不过还是稍有不同。如果要在下半部里面使用自旋锁,可以使用如下表中的API 函数:
函数 | 描述 |
---|---|
void spin_lock_bh(spinlock_t *lock) | 关闭下半部,并获取自旋锁。 |
void spin_unlock_bh(spinlock_t *lock) | 打开下半部,并释放自旋锁。 |