spinlock in linux kernel
spinlock采用busy waiting的实现方式, 无法获取锁时线程一直处于忙等待状态(而不是进入休眠,放弃处理器).
使用spinlock的注意事项
1. 持有spinlock的上下文不能主动放弃处理器. 包括禁止抢占, 不能休眠. 如果中断中也要获取spinlock, 需要禁止中断.2. 持有spinlock的时间越短越好.
3. 锁被持有时, 持有者不允许再次尝试获取该锁.
4. 尽量避免使用多个锁, 否则复杂度会大大提高. 在必须获取多个锁时, 始终以相同的顺序获得.
linux kernel中的spinlock相关接口函数
o 定义时初始化: static DEFINE_SPINLOCK(lock);o 动态初始化: spin_lock_init(lock);
o 获得/释放锁
void spin_lock(spinlock_t *lock);
void spin_unlock(spinlock_t *lock);
o 获得锁之前禁止中断, 并保存之前的中断状态, 以待恢复.
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
o 获得锁之前禁止中断
void spin_lock_irq(spinlock_t *lock);
void spin_unlock_irq(spinlock_t *lock);
o 获得锁之前禁止软中断
void spin_lock_bh(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
arm linux kernel中spinlock的实现
include/linux/spinlock.h对于单核处理器(UP: uniprocessor)和多核处理器(SMP: symmetric multiprocessor), 会包含不同的头文件.
在单核处理器的系统中, spinlock的实现比较简单. spin_lock()只会做禁止抢占动作, spin_lock_irqsave()会禁止中断. 除此之外并没有锁相关的工作.
在多核处理器的系统中, 获得spinlock之后会置一个标志, 下面是arm架构的实现:
arch/arm/include/asm/spinlock.h
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
unsigned long tmp;
__asm__ __volatile__(
"1: ldrex %0, [%1]\n"
" teq %0, #0\n"
WFE("ne")
" strexeq %0, %2, [%1]\n"
" teqeq %0, #0\n"
" bne 1b"
: "=&r" (tmp)
: "r" (&lock->lock), "r" (1)
: "cc");
smp_mb();
}
1. 把lock->lock中的值加载到tmp里. ldrex将&lock->lock这个地址标记为当前CPU独占访问, 此时如果另一个CPU执行后面的strexeq指令, 则会失败返回1. . 这就保证了lock操作的原子操作.
2. 如果tmp为0, 则锁可以被获取, 向&lock->lock中写1. 写成功后退出. strex解除当前CPU对&lock->lock地址的独占.
如果tmp不为0, 则当前CPU通过wfe指令进入省电状态.
3. sev指令或中断发生, CPU继续执行, 则"bne 1b"又跳到步骤1. 这里就是spinlock的自旋操作了.
wfe(wait for event)指令会使当前CPU停止执行, 直到发生了以下任一件事:
1. IRQ/FIQ产生.
2. Data abort异常产生.
3. Debug请求.
4. 其他处理器用sev(set event)指令发出事件消息.