Linux内核同步机制spinlock
spin_lock是什么
在平时的工作中,作为开发人员经常碰到这样的问题:多线程或多进程共享的数据如何进行保护,如果发生进程上下文切换或中断上下文切换都可能使共享数据发生争抢问题。这时候就可以考虑用锁了。如果是进程上下文切换引起的可以考虑用信号量或mutex互斥锁,但如果发生在中断上下文,这时候信号量和mutex就无法使用了,因为这两种锁机制是可以睡眠的,而中断上下文又禁止睡眠,这时,spin_lock就是我们最好的选择了。
- spin_lock是一种死等的锁机制。当发生资源访问冲突的时候,可以有两个选择:一个是死等,一个是挂起当前进程,调度其他进程执行。
- spin_lock一次只允许一个线程进入共享资源区,信号量可以允许多个线程进入。
- spin_lock执行的时间要短,因为spin_lock是死等锁,所以在共享资源区的执行时间不能太长,否则会造成CPU的资源浪费。
- spin_lock最重要的一点是可以在中断上下文执行。
资源竞争
现代CPU一般都是多核多CPU的SMP架构,在这种情况下就有可能出现多个CPU要共同访问同一个资源的问题,此时就需要对资源进行保护,确保同一个时刻只有一个CPU正在访问修改资源变量。锁就是在这种背景下诞生的,锁的种类有很多,应用场景也不同,本篇我们主要介绍spinlock自旋锁。
进程上下文
如果一个全局的资源被多个进程上下文访问,此时,内核是如何执行的呢?对于没有开启内核可抢占
选项的内核,所有的系统调用都是串行执行的,并不存在资源竞争的问题。但是。对于现在的大部分内核来说可抢占
选项是开启的。
假如现在有一个共享资源S,有两个进程A和B,都需要访问共享资源S
执行流程大概是这样的:
进程A访问资源S的时候发生了中断,中断去唤醒优先级更高的进程B,中断返回的时候,发生进程切换,优先级更高的进程B执行,进程B访问共享资源S,如果没有加锁保护,此时进程A和进程B都访问了共享资源S,导致程序的执行结果不正确。如果通过spin_lock加锁保护,进程A访问共享资源S前获取spin_lock,此时发生了中断,优先级更高的进程B开始执行,进程B会去获取spin_lock,由于spin_lock被进程A所持有,导致进程B获取spin_lock失败,进程B会死等直到进程A释放了spin_lock,然后进程B就可以获取spin_lock,访问共享资源S。
中断上下文
- 进程A运行在CPU0上访问共享资源S
- 进程B运行在CPU1上访问共享资源S
- 外部设备发生中断访问共享资源S 此时进程A在CPU0上获取spin_lock开始访问共享资源S,这时外部设备发生了中断,并且在CPU1上执行,假如中断执行了一段时间后被调度到了CPU0上执行,此时会怎样呢?由于CPU0上进程A已经获取了spin_lock,现在被中断上下文打断,中断上下文也要获取spin_lock,那么现在就进入了死胡同也就是死锁状态。而进程B在CPU1上也要获取spin_lock,也会进入死锁状态。所有中断上下文的切换CPU必须禁止本CPU上的中断。
实现原理
spinlock
结构体定义在头文件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;
spinlock
结构体变量的定义有两种,一种是静态定义,一种是动态定义。
#define DEFINE_SPINLOCK(x) spinlock_t x = __SPIN_LOCK_UNLOCKED(x) //静态定义
spinlock_t lock;
spin_lock_init (&lock); //动态定义
spinlock
接口函数介绍,这些函数是驱动编程内核编程的时候会用到的。
位于include/linux/spinlock.h头文件中
static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock)
{
return &lock- >rlock;
}
#define spin_lock_init(_lock) \\
do { \\
spinlock_check(_lock); \\
raw_spin_lock_init(&(_lock)- >rlock); \\
} while (0)
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock- >rlock);
}
static __always_inline void spin_lock_bh(spinlock_t *lock)
{
raw_spin_lock_bh(&lock- >rlock);
}
static __always_inline int spin_trylock(spinlock_t *lock)
{
return raw_spin_trylock(&lock- >rlock);
}
#define spin_lock_nested(lock, subclass) \\
do { \\
raw_spin_lock_nested(spinlock_check(lock), subclass); \\
} while (0)
#define spin_lock_nest_lock(lock, nest_lock) \\
do { \\
raw_spin_lock_nest_lock(spinlock_check(lock), nest_lock); \\
} while (0)
static __always_inline void spin_lock_irq(spinlock_t *lock)
{
raw_spin_lock_irq(&lock- >rlock);
}
#define spin_lock_irqsave(lock, flags) \\
do { \\
raw_spin_lock_irqsave(spinlock_check(lock), flags); \\
} while (0)
#define spin_lock_irqsave_nested(lock, flags, subclass) \\
do { \\
raw_spin_lock_irqsave_nested(spinlock_check(lock), flags, subclass); \\
} while (0)
static __always_inline void spin_unlock(spinlock_t *lock)
{
raw_spin_unlock(&lock- >rlock);
}
static __always_inline void spin_unlock_bh(spinlock_t *lock)
{
raw_spin_unlock_bh(&lock- >rlock);
}
static __always_inline void spin_unlock_irq(spinlock_t *lock)
{
raw_spin_unlock_irq(&lock- >rlock);
}
static __always_inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
raw_spin_unlock_irqrestore(&lock- >rlock, flags);
}
static __always_inline int spin_trylock_bh(spinlock_t *lock)
{
return raw_spin_trylock_bh(&lock- >rlock);
}
static __always_inline int spin_trylock_irq(spinlock_t *lock)
{
return raw_spin_trylock_irq(&lock- >rlock);
}
#define spin_trylock_irqsave(lock, flags) \\
({ \\
raw_spin_trylock_irqsave(spinlock_check(lock), flags); \\
})
static __always_inline int spin_is_locked(spinlock_t *lock)
{
return raw_spin_is_locked(&lock- >rlock);
}
static __always_inline int spin_is_contended(spinlock_t *lock