spinlock
单CPU系统
include/linux/spinlock_api_up.h
#define __LOCK(lock) \
do { preempt_disable(); ___LOCK(lock); } while (0)
#define __UNLOCK(lock) \
do { preempt_enable(); ___UNLOCK(lock); } while (0)
多CPU系统
include/linux/spinlock_api_smp.h
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();
... ...
}
static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
... ...
preempt_enable();
}
semaphore
kernel/locking/semaphore.c
void down(struct semaphore *sem)
{
... ...
raw_spin_lock_irqsave(&sem->lock, flags);
... ...
raw_spin_unlock_irqrestore(&sem->lock, flags);
} // spinlock包含抢占的关闭/打开
mutex
kernel/locking/mutex.c
// __mutex_lock_slowpath
static __always_inline int __sched
__mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
struct lockdep_map *nest_lock, unsigned long ip,
struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)
{
... ...
preempt_disable(); // 关闭抢占
... ...
if (mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx)) {
/* got the lock, yay! */
preempt_enable();
return 0;
}
... ...
spin_lock_mutex(&lock->wait_lock, flags); // 关闭抢占
... ...
for (;;) {
... ...
spin_unlock_mutex(&lock->wait_lock, flags);
schedule_preempt_disabled();
spin_lock_mutex(&lock->wait_lock, flags);
}
... ...
skip_wait:
... ...
spin_unlock_mutex(&lock->wait_lock, flags); // 打开抢占
preempt_enable(); // 打开抢占
return 0;
err:
... ...
spin_unlock_mutex(&lock->wait_lock, flags); // 打开抢占
... ...
preempt_enable(); // 打开抢占
return ret;
}
理解
-
spinlock临界区中关闭抢占。如果打开抢占,临界区内发生中断,中断返回时会检查抢占调度:
- 抢占调度的进程也可能申请spinlock,导致死锁
- 即使没有死锁,抢占调度意味持有spinlock的进程睡眠,不符合忙等待的语义(不能睡眠和快速完成)
因此,spinlock临界区代码不允许抢占,即不能睡眠和主动调度。 -
spinlock虽然关闭抢占,但并不一定关闭中断。如果临界区内发生中断,而中断处理程序也要申请spinlock,中断上下文进入忙等待状态,导致死锁。因此,对可能在中断处理程序中操作的临界区加锁,需要使用spin_lock_irq和spin_lock_irqsave:
static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
local_irq_disable();
... ...
}
static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
{
unsigned long flags;
/* 调用local_irq_disable
* 只关闭本地CPU的中断
* 因为即使其他CPU的中断处理程序试图获取spinlock,本地CPU的临界区也在继续执行,很快会离开临界区并释放锁(忙等待的语义),其他CPU的中断处理程序可以很快获取spinlock */
local_irq_save(flags);
... ...
}
- semaphore和mutex虽然在申请/释放锁的过程中不允许抢占(schedule除外),但临界区中允许抢占:
- 如果临界区内被抢占调度,抢占调度的进程在等待获取锁的过程中会睡眠(schedule),被抢占的临界区理论上可以被重新唤醒
- 但是不允许递归和嵌套(未找到semaphore不可以递归和嵌套的说法),应该是会导致死锁。