版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rikeyone/article/details/86542542
spinlock加锁过程
代码调用流程:
spin_lock
->raw_spin_lock
-->_raw_spin_lock
--->__raw_spin_lock
---->arch_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); //请求锁
}
这里实际的工作都是由LOCK_CONTENDED来做的:
#ifdef CONFIG_LOCK_STAT //如果定义了CONFIG_LOCK_STAT
#define LOCK_CONTENDED(_lock, try, lock) \
do { \
if (!try(_lock)) { \
lock_contended(&(_lock)->dep_map, _RET_IP_); \
lock(_lock); \
} \
lock_acquired(&(_lock)->dep_map, _RET_IP_); \
} while (0)
#else
#define LOCK_CONTENDED(_lock, try, lock) \ //如果没有定义CONFIG_LOCK_STAT
lock(_lock)
#endif
void do_raw_spin_lock(raw_spinlock_t *lock)
{
debug_spin_lock_before(lock);
if (unlikely(!arch_spin_trylock(&lock->raw_lock))) //先try一次去获取,如果不成功,那么在进一步使用spin lock获取
__spin_lock_debug(lock);
debug_spin_lock_after(lock);
}
int do_raw_spin_trylock(raw_spinlock_t *lock)
{
int ret = arch_spin_trylock(&lock->raw_lock);
if (ret)
debug_spin_lock_after(lock);
#ifndef CONFIG_SMP
/*
* Must not happen on UP:
*/
SPIN_BUG_ON(!ret, lock, "trylock failure on UP");
#endif
return ret;
}
我们先来看spin lock的情况,它先进行try一次,失败后执行__spin_lock_debug,这里会进行实际的spin lock处理:
static void __spin_lock_debug(raw_spinlock_t *lock)
{
u64 i;
u64 loops = loops_per_jiffy * HZ;
for (i = 0; i < loops; i++) {
if (arch_spin_trylock(&lock->raw_lock))
return;
__delay(1);
}
/* lockup suspected: */
spin_dump(lock, "lockup suspected");
#ifdef CONFIG_SMP
trigger_all_cpu_backtrace();
#endif
/*
* The trylock above was causing a livelock. Give the lower level arch
* specific lock code a chance to acquire the lock. We have already
* printed a warning/backtrace at this point. The non-debug arch
* specific code might actually succeed in acquiring the lock. If it is
* not successful, the end-result is the same - there is no forward
* progress.
*/
arch_spin_lock(&lock->raw_lock);
}
可以看到此函数最后一句是arch_spin_lock平台相关的操作,但是在使用平台函数加锁之前,还有很多个处理,上面会通过一个for循环不停的trylock,为什么要这么做?
因为我们的arch_spin_lock操作实际最终会使用wfe操作使CPU进入standby状态,但是这个状态维持多久,我们很难统计一个时间出来,但是通过我们的for循环,它会不停进行运算,那么时间就可以统计出来了,这里统计的时间为一个HZ,也就是1S,如果超过1S我们还没有获取到一个spinlock,那么就可以怀疑这时候发生了死锁,会打印对应的log和backtrace。这个处理也是新版本加入的功能,旧版本的spinlock可能压根不经过这个for循环处理了,而是直接进入wfe去等待锁。
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
unsigned long tmp;
u32 newval;
arch_spinlock_t lockval;
__asm__ __volatile__(
"1: ldrex %0, [%3]\n" //把lockval读到tmp, 并且(分别在Local monitor和Global monitor中)设置独占标志
" add %1, %0, %4\n" //lockval + (1 << TICKET_SHIFT) 存到newval。(next票号+1)
" strex %2, %1, [%3]\n" //把newval保存到&lock->slock中,此时 Exclusive monitors会发挥作用,将保存是否成功的标志放入tmp中。
" teq %2, #0\n" //测试strex是否成功(tmp ?= 0 )
" bne 1b" //如果发现strex失败,从1:再次执行
: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
while (lockval.tickets.next != lockval.tickets.owner) { //查看本次获取的票号是否到号
wfe(); //不想等说明没有到号,进入wfe继续等待
lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);
}
smp_mb();
}
spinlock解锁过程
spin_unlock
->raw_spin_unlock
-->_raw_spin_unlock
--->__raw_spin_unlock
---->arch_spin_unlock
static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
spin_release(&lock->dep_map, 1, _RET_IP_);
do_raw_spin_unlock(lock);
preempt_enable();
}
void do_raw_spin_unlock(raw_spinlock_t *lock)
{
debug_spin_unlock(lock);
arch_spin_unlock(&lock->raw_lock);
}
static inline void debug_spin_unlock(raw_spinlock_t *lock)
{
SPIN_BUG_ON(lock->magic != SPINLOCK_MAGIC, lock, "bad magic"); //当没获得spin lock 就释放,会报错
SPIN_BUG_ON(!raw_spin_is_locked(lock), lock, "already unlocked"); //当已经释放spin lock再想释放,会报错
SPIN_BUG_ON(lock->owner != current, lock, "wrong owner"); //不是持有该spin lock的进程来释放该锁,会报错
SPIN_BUG_ON(lock->owner_cpu != raw_smp_processor_id(), //不是持有该spin lock的CPU来释放该锁,会报错
lock, "wrong CPU");
lock->owner = SPINLOCK_OWNER_INIT;
lock->owner_cpu = -1;
}
static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
smp_mb(); //内存屏障
lock->tickets.owner++; //叫下个票号
dsb_sev(); //唤醒WFE状态的CPU
}