自旋锁(spin_lock)
自旋锁在发现锁被占用时,将陷入忙等状态,反复执行紧凑循环指令,直到锁被释放。
自旋锁的循环指令表示“忙等”。即使等待过程中内核控制路径无事可做(除了浪费时间),也占用CPU,保持在CPU上运行。
在Linux中,每个自旋锁都用一个spinlock_t表示,其中包含两个字段:
struct {
raw_spinlock_t raw_lock;//表示状态,1表示未加锁,任何负数和0表示加锁状态;
unsigned int break_lock;//仅在内核支持SMP和内核抢占的时候支持;
} spinlock_t;
raw_spinlock_t {volatile unsigned int lock;}
自旋锁相关函数或宏定义:
spin_lock_init() 把自旋锁设置为1;
spin_lock 循环直到自旋锁变为1,然后把自旋锁置为0;
spin_unlock,把自旋锁置为1;
spin_unlock_wait等待,直到自旋锁变为1;
spin_is_locked
spin_trylock把自旋锁置为0,若原来是1,则返回1,否则返回0;
_spin_lock函数实现:
void __lockfunc _spin_lock(spinlock_t *lock) {
__spin_lock(lock);
}
__lockfunc = __attribute__ ((section(".spinlock.text")))
实际执行lock操作源码:
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
unsigned long tmp0, tmp1;
/*
* lock->slock : =1 : unlock
* : <=0 : lock
*
* for ( ; ; ) {
* lock->slock -= 1; <-- need atomic operation
* if (lock->slock == 0) break;
* for ( ; lock->slock <= 0 ; );
* }
*/
__asm__ __volatile__ (
"# __raw_spin_lock \n\t"
".fillinsn \n"
"1: \n\t"
"mvfc %1, psw; \n\t"
"clrpsw #0x40 -> nop; \n\t"
DCACHE_CLEAR("%0", "r6", "%2")
"lock %0, @%2; \n\t"
"addi %0, #-1; \n\t"
"unlock %0, @%2; \n\t"
"mvtc %1, psw; \n\t"
"bltz %0, 2f; \n\t" //跳转
LOCK_SECTION_START(".balign 4 \n\t")
".fillinsn \n"
"2: \n\t"
"ld %0, @%2; \n\t"
"bgtz %0, 1b; \n\t" //跳转
"bra 2b; \n\t" //跳转
LOCK_SECTION_END
: "=&r" (tmp0), "=&r" (tmp1)
: "r" (&lock->slock)
: "memory" //告诉编译器,此段代码设定优化屏障;
#ifdef CONFIG_CHIP_M32700_TS1
, "r6"
#endif /* CONFIG_CHIP_M32700_TS1 */
);
}
这里注意到注释代码,实际为lock过程;
* for ( ; ; ) {
* lock->slock -= 1; <-- need atomic operation
* if (lock->slock == 0) break;
* for ( ; lock->slock <= 0 ; );
* }
*/
如何控制lock->slock不会始终减一?
这里通过__raw_spin_trylock来进行加锁预判定,如下:
/**
* __raw_spin_trylock - Try spin lock and return a result
* @lock: Pointer to the lock variable
*
* __raw_spin_trylock() tries to get the lock and returns a result.
* On the m32r, the result value is 1 (= Success) or 0 (= Failure).
*/
static inline int __raw_spin_trylock(raw_spinlock_t *lock)
{
int oldval;
unsigned long tmp1, tmp2;
/*
* lock->slock : =1 : unlock
* : <=0 : lock
* {
* oldval = lock->slock; <--+ need atomic operation
* lock->slock = 0; <--+
* }
*/
__asm__ __volatile__ (
"# __raw_spin_trylock \n\t"
"ldi %1, #0; \n\t"
"mvfc %2, psw; \n\t"
"clrpsw #0x40 -> nop; \n\t"
DCACHE_CLEAR("%0", "r6", "%3")
"lock %0, @%3; \n\t"
"unlock %1, @%3; \n\t"
"mvtc %2, psw; \n\t"
: "=&r" (oldval), "=&r" (tmp1), "=&r" (tmp2)
: "r" (&lock->slock)
: "memory"
#ifdef CONFIG_CHIP_M32700_TS1
, "r6"
#endif /* CONFIG_CHIP_M32700_TS1 */
);
return (oldval > 0);
}
解锁
static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
mb();//asm volatile("mfence":::"memory")或者asm volatile("lock; addl $0, 0(%esp)")
lock->slock = 1;
}
如上源码可解释为如下:
具有内核抢占的spin_lock宏:
1、调用preempt_disable以禁用内核抢占;
2、调用_raw_spin_trylock,对自旋锁的slock字段执行原子性的测试和设置操作;
3、如果自旋锁中的旧值是正数,宏结束:内核控制路径已获得自旋锁;
4、否则,内核控制路径无法获得自旋锁,因此宏必须执行循环一直到其他CPU释放自旋锁。调用preempt_enable开启内核抢占。如果在执行spin_lock之前内核抢占被启用,那么其他进程此时可以取代等待自旋锁的进程;
5、执行循环等待:
while(spin_is_locked(slp) && slp->break_lock)
cpu_relax;//==》pause(rep, nop),可以减少能源消耗,延迟较短,
加快了紧跟在后面的代码的执行,并减少能源消耗;优化自旋锁执行效率
6、跳到第一步,再次试图获取自旋锁;
非抢占式spin_lock宏:(实际情况:标记2处代码包含在另外的段中,以便在大多数情况下不要用不执行的代码填充硬件高速缓存)
1:lock; decb slp->slock;// 前缀lock;decb原子减一
jns 3f//检测符号标志,如果被清零,则调到标记3出继续执行;f表示向前;
pause
2: compb $0, slp->slock//否则在标记2处执行紧凑循环,直到自旋锁出现正值。然后从标签1处重新开始执行(b表示向后)
jle 2b
jmp 1b
3: …
spin_unlock宏:
movb $1, slp->slock;//现在的80x86微处理器总是原子的执行内存中的只写访问,所以不用lock前缀;
并在随后调用preempt_enable;