1、spin_lock
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
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 会去关当前核调度,但是不会关当前核中断。
想象这样一个场景,一个外设处理线程正在尝试获取自旋锁 A,而这时外设中断产生,中断处理里面也尝试获取这把锁 A,就会出现死锁的现象。
这里有一个前提,被中断打断的线程,不会再参与操作系统调度。只能等待中断执行结束、恢复中断上下文去执行被打断的线程
还有一点要注意的是,因为会关闭当前核调度,所以 spin_lock 所保护的临界区禁止调用可能引起睡眠接口。如果自旋锁锁住以后进入睡眠,而此时又不能进行当前核的调度,从而导致该 CPU 被挂起。
2、spin_lock_irq
static __always_inline void spin_lock_irq(spinlock_t *lock)
{
raw_spin_lock_irq(&lock->rlock);
}
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);
}
从代码可以看到,spin_lock_irq
会去不仅关当前核调度,还会关当前核中断。这就解决了上面提到的死锁问题。
但这会带来另一个问题,就是如果自旋锁所保护的临界区执行时间太长,当前核一直处于关中断状态,会极大影响操作系统的实时性。这一点就要求开发人员要注意,自旋锁所保护的临界区执行时间一定不能过长!
对应的,spin_unlock_irq
会去开当前核中断。这会带来一个问题,如果调用 spin_lock_irq
之前当前核处于关中断状态,而因为你调用了 spin_unlock_irq
,把当前核的中断强行打开了,这会带来不可预知的问题!
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);
}
2、spin_lock_irqsave
#define spin_lock_irqsave(lock, flags) \
do { \
raw_spin_lock_irqsave(spinlock_check(lock), flags); \
} while (0)
/*
* If lockdep is enabled then we use the non-preemption spin-ops
* even on CONFIG_PREEMPTION, because lockdep assumes that interrupts are
* not re-enabled during lock-acquire (which the preempt-spin-ops do):
*/
#if !defined(CONFIG_GENERIC_LOCKBREAK) || defined(CONFIG_DEBUG_LOCK_ALLOC)
static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
{
unsigned long flags;
local_irq_save(flags);
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
return flags;
}
spin_lock_irqsave
完美的解决了上面提出的各种问题,是最安全的自旋锁。该函数增加了一个参数,即在关闭当前核中断之前,将当前核的中断状态保存在 flags 变量中。等到需要spin_unlock_irqrestore
解锁时,只恢复 flag,并不去强制打开中断。
下面我们来看下它具体是怎么实现的。
#define raw_local_irq_save(flags) \
do { \
typecheck(unsigned long, flags); \
flags = arch_local_irq_save(); \
} while (0)
以 ARM32 架构为例,这是一段内联汇编。
#define arch_local_irq_save arch_local_irq_save
static inline unsigned long arch_local_irq_save(void)
{
unsigned long flags;
asm volatile(
" mrs %0, " IRQMASK_REG_NAME_R " @ arch_local_irq_save\n"
" cpsid i"
: "=r" (flags) : : "memory", "cc");
return flags;
}
mrs %0, IRQMASK_REG_NAME_R
IRQMASK_REG_NAME_R
是一个宏,在 ARM32 架构下是 cpsr 寄存器。该句汇编的含义是,读取 cpsr 寄存器中当前核的中断状态,保存在 flag 参数中
cpsid i
- 该句汇编的含义是,关闭当前 CPU 的本地 IRQ 中断
#define arch_local_irq_restore arch_local_irq_restore
static inline void arch_local_irq_restore(unsigned long flags)
{
asm volatile(
" msr " IRQMASK_REG_NAME_W ", %0 @ local_irq_restore"
:
: "r" (flags)
: "memory", "cc");
}
static inline void __raw_spin_unlock_irqrestore(raw_spinlock_t *lock,
unsigned long flags)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
local_irq_restore(flags);
preempt_enable();
}
从这里看到,spin_unlock_irqrestore
解锁时,不会直接打开当前核中断,只会恢复上一次 cpsr 寄存器的值(也就是入参 flags)。