spinlock是linux内核锁机制的一种,而linux内核锁机制是linux内核同步机制的一部分。
linux内核同步机制的使用原因是为了避免共享数据之间的竞争出现,它包括per cpu变量、原子操作、内存屏障、spinlock、信号量、顺序锁、禁止本地中断、禁止本地软中断、RCU等等。linux内核同步机制与SMP、抢占、可延迟函数、工作队列等等紧密关联。
由于复杂性的原因,在此并不对整个linux内核同步机制进行全面的讲解,而仅仅对于其中的一个问题进行分析,即:在linux内核代码中持有spinlock时为什么不能够睡眠。
我们使用ULK所介绍的Linux内核2.6.11版本。
首先,本质原因是spinlock的设计目的是保证数据修改的原子性,因此没有理由在spinlock锁住的区域内停留。
然后我们来看具体实现上的原因。阅读内核源码之后发现,持有spinlock时为什么不能够睡眠的原因与SMP和内核抢占紧密相关。所以我们分以下四种情况来讨论:
1. 单处理器不可抢占(!CONFIG_SMP && !CONFIG_PREEMPT):
这种配置下,在内核中与spinlock相关的具体实现如下
#define _spin_lock(lock) \
do { \
preempt_disable(); \空
_raw_spin_lock(lock); \空
__acquire(lock); \空
} while(0)
#define preempt_disable() do { } while (0)
#define _raw_spin_lock(lock) do { (void)(lock); } while(0)
即,实际上此时spin_lock()是空操作。
a. 对于spinlock来说,在此种配置下,正常情况:当前进程只能够被中断抢占(如果使用spin_lock_irq()甚至中断都不能够抢占),其他任何进程都不能换入,直到本进程完成临界区的执行。如果持有spinlock时睡眠:则会换入其他进程,可能对临界区数据进行修改,根本上破坏了使用锁的目的。
b. 对于信号量来说,在此种配置下,进程持有信号量然后睡眠,即使换入的其他进程想对临界区做修改,也因为信号量的阻隔不能成立。
2. 单处理器可抢占(!CONFIG_SMP && CONFIG_PREEMPT):
这种配置下,在内核中与spinlock相关的具体实现如下
#define _spin_lock(lock) \
do { \
preempt_disable(); \
_raw_spin_lock(lock); \空
__acquire(lock); \空
} while(0)
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
实际此时spin_lock()上仅仅做了关闭抢占的操作,而且在关闭抢占之后就与第一种单处理器不可抢占的情况完全相同了。
3. 多处理器不可抢占(CONFIG_SMP && !CONFIG_PREEMPT):
这种配置下,在内核中与spinlock相关的具体实现如下
void __lockfunc _spin_lock(spinlock_t *lock)