使用自旋锁的场景
临界区在中断上下文中不能被调度出去的场景。
先说明下中断上下文不能被调度出来的原因。
中断上下文中唯一能打断当前中断的是更高优先级的中断。进程是不能打断中断的。
在linux的中断模型中,中断的上半部是没有优先级的,所有来的中断将尽力处理。这时候对于local cpu来说一旦拥有cpu,就不能睡眠,否则就无法被唤醒。
这点对于softirq,tasklet也一样,因此这些bottom half也不能休眠),这样中断上下文中发生了调度,导致了休眠是无法再被唤醒的。
另个schedule()在切换进程时,保存当前的进程上下文(CPU寄存器的值、进程的状态以及堆栈中的内容),以便以后恢复此进程运行。中断发生后,内核会先保存当前被中断的进程上下文(在调用中断处理程序后恢复);但在中断处理程序里,CPU寄存器的值肯定已经变化了吧(最重要的程序计数器PC、堆栈SP等),如果此时因为睡眠或阻塞操作调用了schedule(),则保存的进程上下文就不是当前的进程context了.所以不可以在中断处理程序中调用schedule()。
如果中断处理时,禁止了内核抢占,如果休眠,会导致内核挂起。
导致中断上下文被调度出去的场景:schedule(),休眠,IO例程
临界区小,普通的锁会导致切换,性能损耗太大。
自旋锁的原理
所有自旋锁的变体都会禁止内核抢占。重点强调下:禁止了抢占并不意味着CPU就能由当前进程一直独占,中断仍可以获得CPU。所以变体的一个主要方向是添加参中断的禁止。
考虑简单的情形:内核线程A获取到了自旋锁,禁止抢占本地CPU,进入临界区执行时。这时对于单CPU,在禁止抢占的情况下,其他进程是无法通过调度获得CPU,只有通过中断的方式获得CPU。在单CPU的情况下,是不用关注进程调度的问题,主要考虑中断抢占CPU的情况。
内核线程B在执行spin_lock时,执行禁止抢占,不停地自旋,等待锁资源。
- __raw_spin_lock
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);
}
- __raw_spin_lock_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);
}
- __raw_spin_lock_irqsave
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_);
/*
* On lockdep we dont want the hand-coded irq-enable of
* do_raw_spin_lock_flags() code, because lockdep assumes
* that interrupts are not re-enabled during lock-acquire:
*/
#ifdef CONFIG_LOCKDEP
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
#else
do_raw_spin_lock_flags(lock, &flags);
#endif
return flags;
}
- arch_local_irq_save
static __always_inline unsigned long arch_local_irq_save(void)
{
unsigned long flags = arch_local_save_flags(); // 保存中断状态
arch_local_irq_disable(); // 关闭中断
return flags;
}
自旋锁的使用方法及注意点
使用自旋锁,有两种方式定义一个锁:
动态的:
spinlock_t lock;
spin_lock_init (&lock);
静态的:
DEFINE_SPINLOCK(lock);
使用时的注意点
临界区内能不能睡眠
相关引用
https://www.cnblogs.com/ggzhangxiaochao/p/15874982.html