前情提要:
Linux中的并发与竞争(1)并发是如何发生的以及原子操作介绍
1.自旋锁概念及优缺点
自旋锁(spin lock):对共享资源进行互斥访问,采用循环加锁 -> 等待的机制。当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。自旋锁只使用短时间的轻量级加锁。
自旋锁的优点:如果持有锁的线程能在短时间内释放锁资源,那么竞争锁就不需要做内核态和用户态之间的切换,只需要等到持有锁的线程释放锁,避免了用户进程和内核切换的消耗。
自旋锁的缺点:长时间上锁的话,自旋锁会非常耗费性能,它阻止了那些等待锁的线程运行和调度。
2.自旋锁API
2.1普通自旋锁
Linux内核使用 spinlock_t表示自旋锁 ,在include/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;
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。
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) //将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。
2.2读写自旋锁
读写自旋锁:为读和写提供了不同的锁,当没有写操作时,允许多个线程使用读锁,进行并发的读取操作。更适用于读取场合更多的情况
Linux内核中使用 rwlock_t 结构体表示读写锁
3.自旋锁配合中断使用
DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */
/* 线程 A */
void functionA (){
unsigned long flags; /* 中断状态 */
spin_lock_irqsave(&lock, flags) /* 获取锁 */
/* 临界区 */
spin_unlock_irqrestore(&lock, flags) /* 释放锁 */
}
/* 中断服务函数 */
void irq() {
spin_lock(&lock) /* 获取锁 */
/* 临界区 */
spin_unlock(&lock) /* 释放锁 */
}
在进入临界区时,线程A获取到锁,并且保存中断状态,禁止了中断,防止被中断服务打断而造成死锁现象。