1),spin lock 结构体
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} raw_spinlock_t;
typedef struct {
union {
u32 slock;
struct __raw_tickets {
#ifdef __ARMEB__
u16 next;
u16 owner;
#else
u16 owner;
u16 next;
#endif
} tickets;
};
} arch_spinlock_t;
暂且抛开那些CONFIG 宏定义,其实就是arch_spinlock_t这个结构体。
这个结构体是32bits大小。用了union。slock和 next owner共用这32bits。
旧版本的内核spin lock结构体只有一个u32 ,为0则可以获得锁,不为0就自旋等待。 新的自旋锁加了next和owner让线程可以按获取锁的顺序排队。
2),获取锁
static inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
没什么好说,直接到raw_spin_lock,然后中间经过数次调用,或者宏定义(其中有区分SMP和UP等)
最终到
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);
}
1,关闭抢占,同一cpu上靠关闭抢占解决同步问题,UP上spin lock 其实就是关闭抢占
2,这个是编译时静态检查相关,保证解锁加锁成对出现
3,这个宏就最终调用了do_raw_spin_lock,再到arch_spin_lock
3)arch_spin_lock
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"
" add %1, %0, %4\n"
" strex %2, %1, [%3]\n"
" teq %2, #0\n"
" bne 1b"
: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
while (lockval.tickets.next != lockval.tickets.owner) {
wfe();
lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);
}
smp_mb();
}
从第8行开始是嵌入了asm 汇编, __volatile__关键字确保下面代码不被优化改变。
根据嵌入汇编的规则,%0 是lockval ,类型为arch_spinlock_t类型,%1 %2 依次是newval 和tmp,依次类推。
汇编
第1行: 把lock->slock值保存到lock_val
第2行:newval=lockval+1< TICKET_SHIFT 是 用来表明arch_spinlock_t中owner 和next各自所占多少bit,定义为16则,owner和next各占16bit 加1< 第3,4行:lock->slock = newval ,判断strex返回值,确认是否成功更新锁,如果更新失败,说明有其他内核路径插入。 ldrex 和strex 用这种方法保证原子性,所以同一时间只会有一个线程能成功更新lock->slock的值。 同样内核中原子操作也是用类似的方法实现。 第5行: 如果更新失败回到1行。 如果更新成功,到下面c代码 判断next是否等于owner,如果相等就获得锁,如果不相等说明还没轮到自己,调用wfe()挂起当前cpu。 释放锁操作 释放锁比较简单,直接owner++,然后sev指令通知所有cpu,其他cpu会尝试获得锁。 思考一下为什么释放锁不需要用原子操作的方式操作owner,而获取锁需要用原子方式操作next? 另外 用smp_mb()数据内存屏障保证乱序执行时,内存操作的顺序。static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
smp_mb();
lock->tickets.owner++;
dsb_sev();
}