linux代码之spin lock

/*
 * 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的操作。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值