Linux 驱动开发 二十七:自旋锁

内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:

  • 一个是原地等待
  • 一个是挂起当前进程,调度其他进程执行(睡眠)

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) 打开下半部,并释放自旋锁。
上一篇:树莓派 (Raspberry Pi) 是什么?普通人怎么玩?(私有云NAS也会有;上传到百度盘的功能nas也有)


下一篇:Revit二开学习-多排管道标注