/*
* 2020/11/30 16:33 qing
*
* spin lock 是architecture dependent代码
*/
/*
* spin_lock 单核(UP),多核(SMP)
*
* 首先定义一个spinlock_t的数据类型,其本质上是一个整数值(对该数值的操作需要保证原子性),该数值表示spin lock是否可用。
* 初始化的时候被设定为1。当thread想要持有锁的时候调用spin_lock函数,该函数将spin lock那个整数值减去1,然后进行判断,
* 如果等于0,表示可以获取spin lock,如果是负数,则说明其他thread的持有该锁,本thread需要spin。
*
* 一个整数就OK了,0表示unlocked,1表示locked。配套的API包括__raw_spin_lock和__raw_spin_unlock。__raw_spin_lock会持续判断lock的值是否等于0,
* 如果不等于0(locked)那么其他thread已经持有该锁,本thread就不断的spin,判断lock的数值,一直等到该值等于0为止,一旦探测到lock等于0,
* 那么就设定该值为1,表示本thread持有该锁了,当然,这些操作要保证原子性,细节和exclusive版本的ldr和str(即ldrex和strexeq)相关。
* 立刻临界区后,持锁thread会调用__raw_spin_unlock函数是否spin lock,其实就是把0这个数值赋给lock。
*
* 早期spin_lock存在的不公平性,
*
* 在现代多核的cpu中,因为每个cpu都有chach的存在,导致不需要去访问主存获取lock,所以当当前获取lock的cpu,释放锁后,
* 使其他cpu的cache都失效,然后释放的锁在下一次就比较容易进入临界去,导致出现了不公平。
*
* lock本质上是保存在main memory中的,由于cache的存在,当然不需要每次都有访问main memory。在多核架构下,每个CPU都有自己的L1 cache,
* 保存了lock的数据。假设CPU0获取了spin lock,那么执行完临界区,在释放锁的时候会调用smp_mb invalide其他忙等待的CPU的L1 cache,
* 这样后果就是释放spin lock的那个cpu可以更快的访问L1cache,操作lock数据,从而大大增加的下一次获取该spin lock的机会。
*
* 现采用 ticket-based spin lock 机制
*
* tickets中的owner和next的含义。详细可见提交:
* 546c2896a42202dbc7d02f7c6ec9948ac1bf511b
*
* 因为有cache的作用,导致本次释放lock的cpu在下一次就可以更快的获取锁。所以在ARMv6上引入了”票”算法来保证每个cpu都是像“FIFO“访问临界区。
*
* 在ticket中。刚开始的时候临界区没有cpu进入,状态是空闲的。next和owner的值都是0,当cpu1进入临界区后。
* 将next++, 当cpu1从临界区域执行完后,将owner++。这时候next和owner都为1,说明临界区没有cpu进入。这时候cpu2进入临界区,将next++,
* 然后cpu2好像干的活比较多,当cpu3进来后,next++,这时候next已经是3了,当cpu2执行完毕后,owner++,owner的值变为2,
* 表示让cpu2进入临界区,这就保障了各个cpu之间都是先来后到的执行。
*/
/*
* spinlock_t
*/
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;
/*
* raw_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;
/*
* arch_spinlock_t
*/
typedef struct {
#ifdef __AARCH64EB__
u16 next;
u16 owner;
#else
u16 owner;
u16 next;
#endif
} __aligned(4) arch_spinlock_t;
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
__raw_spin_lock(lock);
}
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable(); /* 禁止抢占 */
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); /* CONFIG_DEBUG_LOCK_ALLOC 和运行时检查锁的有效性有关的 */
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); /* __acquire和静态代码检查相关 */
}
static inline int do_raw_spin_trylock(raw_spinlock_t *lock)
{
return arch_spin_trylock(&(lock)->raw_lock);
}
/* 尝试去获取spin lock */
static inline int arch_spin_trylock(arch_spinlock_t *lock)
{
unsigned int tmp;
arch_spinlock_t lockval;
asm volatile(ARM64_LSE_ATOMIC_INSN(
/* LL/SC */
" prfm pstl1strm, %2\n" /* 将lock变量读到cache,增加访问速度 , l1 : Level 1 cache , STRM流或非临时预取,用于只使用一次的数据*/
"1: ldaxr %w0, %2\n" /* lockval = *lock */
" eor %w1, %w0, %w0, ror #16\n" /* tmp = lockval ^ lockval << 16 */
" cbnz %w1, 2f\n" /* 比较tmp,如果结果非零(Non Zero)就转移(只能跳到后面的指令) 1MB */
" add %w0, %w0, %3\n" /* lockval= lockval + (1 << TICKET_SHIFT) */
" stxr %w1, %w0, %2\n" /* lockval + *lock = tmp */
" cbnz %w1, 1b\n" /* compare tmp */
"2:",
/* LSE atomics */
" ldr %w0, %2\n"
" eor %w1, %w0, %w0, ror #16\n"
" cbnz %w1, 1f\n"
" add %w1, %w0, %3\n"
" casa %w0, %w1, %2\n"
" and %w1, %w1, #0xffff\n"
" eor %w1, %w1, %w0, lsr #16\n"
"1:")
: "=&r" (lockval), "=&r" (tmp), "+Q" (*lock)
: "I" (1 << TICKET_SHIFT)
: "memory");
return !tmp;
}
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
unsigned int tmp;
arch_spinlock_t lockval, newval;
asm volatile(
/* Atomically increment the next ticket. */
ARM64_LSE_ATOMIC_INSN(
/* LL/SC */
" prfm pstl1strm, %3\n" /* 将lock变量读到cache,增加访问速度 , l1 : Level 1 cache , STRM流或非临时预取,用于只使用一次的数据*/
"1: ldaxr %w0, %3\n" /* lockval = lock */
" add %w1, %w0, %w5\n" /* newval=lockval + (1 << 16), 相当于next++ */
" stxr %w2, %w1, %3\n" /* lock = newval */
" cbnz %w2, 1b\n", /* if(tmp != 0)goto 1 */
/* LSE atomics */
" mov %w2, %w5\n"
" ldadda %w2, %w0, %3\n"
__nops(3)
)
/* Did we get the lock? */
" eor %w1, %w0, %w0, ror #16\n" /* if(next == owner), lockval中的next域就是自己的号码牌,判断是否等于owner */
" cbz %w1, 3f\n" /* if(newval == 0), 如果等于,持锁进入临界区 */
/*
* No: spin on the owner. Send a local event to avoid missing an
* unlock before the exclusive load.
*/
" sevl\n"
"2: wfe\n" /* 自旋等待 */
" ldaxrh %w2, %4\n" /* tmp = lock->owner, 其他cpu唤醒本cpu,获取当前owner值 */
" eor %w1, %w2, %w0, lsr #16\n" /* if(next == owner), 自己的号码牌是否等于owner */
" cbnz %w1, 2b\n" /* 如果等于,持锁进入临界区,否者回到2,即继续spin */
/* We got the lock. Critical section starts here. */
"3:"
: "=&r" (lockval), "=&r" (newval), "=&r" (tmp), "+Q" (*lock)
: "Q" (lock->owner), "I" (1 << TICKET_SHIFT)
: "memory");
}
static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
unsigned long tmp;
asm volatile(ARM64_LSE_ATOMIC_INSN(
/* LL/SC */
" ldrh %w1, %0\n"
" add %w1, %w1, #1\n"
" stlrh %w1, %0",
/* LSE atomics */
" mov %w1, #1\n"
" staddlh %w1, %0\n"
__nops(1))
: "=Q" (lock->owner), "=&r" (tmp)
:
: "memory");
}
解锁的操作相对简单,就是给owner执行加1的操作。