linux代码之rwlock

/*
 * 2020/12/1    16:03    qing
 */

/*
 * 基本原理
 */
    spin_lock只允许一个thread进入临界区,而且对进入临界区中的操作不做细分。但是在实际中,对临界区的操作分为读和写。
    如果按照spin_lock的实现,当多个read thread都想进入临界区读取的时候,这时候只有一个read thread进入到临界区,这样效率和性能明显下降。
    所以就针对某些操作read thread占绝大多数的情况下,提出了读写锁的概念。


    加锁操作

        假设当前临界区没有任何进程,这时候read进程或者write进程都可以进来,但是只能是其一
        如果当前临界区只有一个read进程,这时候任意的read进程都可以进入,但是write进程不能进入
        如果当前临界区只有一个write进程,这时候任何read/write进程都无法进入。只能自旋等待
        如果当前当前临界区有好多个read进程,同时read进程依然还会进入,这时候进入的write进程只能等待。直到临界区一个read进程都没有,才可进入

    解锁操作

        如果在read进程离开临界区的时候,需要根据情况决定write进程是否需要进入。只有当临界区没有read进程了,write进程方可进入。
        如果在write进程离开临界区的时候,无论write进程或者read进程都可进入临界区,因为write进程是排它的。

    加锁API

        read_lock(lock)/write_lock(lock)                                              # 获取指定的锁
        read_trylock(lock)/write_trylock(lock)                                        # 尝试获取锁,如果失败不spin,直接返回
        read_lock_irq(lock)/write_lock_irq(lock)                                      # 获取指定的锁,同时关掉本地cpu中断
        read_lock_irqsave(lock, flags)/write_lock_irqsave(lock, flags)                # 保存本地cpu的irq标志,然后关掉cpu中断,获取指定锁
        read_lock_bh(lock)/read_lock_bh(lock)                                         # 获取指定的锁,同时关掉中断下半部(bottom half)

    解锁API

        read_unlock(lock)/write_unlock(lock)                                          # 释放指定的锁
        read_unlock_irq(lock)/write_unlock_irq(lock)                                  # 释放指定的锁,同时使能cpu中断
        read_unlock_irqrestore/write_unlock_irqrestore                                # 释放锁,同时使能cpu中断,恢复cpu的标识
        read_unlock_bh/write_unlock_bh                                                # 释放锁,同时使能cpu中断的下半部

/*
 * rwlock_t
 */

typedef struct {
    arch_rwlock_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
} rwlock_t;

/*
 * arch_rwlock_t
 */
typedef struct {
    volatile unsigned int lock;
} arch_rwlock_t;

/*
 * Write lock implementation.
 * 写锁实现
 *
 * Write locks set bit 31. Unlocking, is done by writing 0 since the lock is
 * exclusively held.
 * 写入位锁31。解锁,是通过写入0来完成的,因为锁是以独占方式持有的
 *
 * The memory barriers are implicit with the load-acquire and store-release
 * instructions.
 * 在加载获取和存储释放指令中,内存屏障是隐式的
 *
 *
 * 对于wirte操作,只要临界区有read/write进程存在,就需要自旋等待,直到临界区没有任何进程存在
 *
 * 写锁只对31bit进行操作,即31bit为1为有写锁,0为无
 */
static inline void arch_write_lock(arch_rwlock_t *rw)
{
    unsigned int tmp;

    asm volatile(ARM64_LSE_ATOMIC_INSN(
    /* LL/SC */
    "    sevl\n"
    "1:    wfe\n"                            /* 使cpu进入低功耗模式  */
    "2:    ldaxr    %w0, %1\n"                /* 读取锁的值,赋值给tmp变量  */
    "    cbnz    %w0, 1b\n"                /* 如果tmp的值不为0, 跳转到标号1重新执行。!=0说明有read/write进程正在持有锁,所以需要进入低功耗等待 */
    "    stxr    %w0, %w2, %1\n"            /* 将锁的bit31设置为1, 然后将设置结果放入tmp中 */
    "    cbnz    %w0, 2b\n"                /* 如果tmp的值不为0,说明上条指令执行失败,跳转到标号2继续执行 */
    __nops(1),
    /* LSE atomics */
    "1:    mov    %w0, wzr\n"
    "2:    casa    %w0, %w2, %1\n"
    "    cbz    %w0, 3f\n"
    "    ldxr    %w0, %1\n"
    "    cbz    %w0, 2b\n"
    "    wfe\n"
    "    b    1b\n"
    "3:")
    : "=&r" (tmp), "+Q" (rw->lock)
    : "r" (0x80000000)
    : "memory");
}

static inline int arch_write_trylock(arch_rwlock_t *rw)
{
    unsigned int tmp;

    asm volatile(ARM64_LSE_ATOMIC_INSN(
    /* LL/SC */
    "1:    ldaxr    %w0, %1\n"
    "    cbnz    %w0, 2f\n"
    "    stxr    %w0, %w2, %1\n"
    "    cbnz    %w0, 1b\n"
    "2:",
    /* LSE atomics */
    "    mov    %w0, wzr\n"
    "    casa    %w0, %w2, %1\n"
    __nops(2))
    : "=&r" (tmp), "+Q" (rw->lock)
    : "r" (0x80000000)
    : "memory");

    return !tmp;
}

/*
 * 解锁, 将锁的值全部清0
 */
static inline void arch_write_unlock(arch_rwlock_t *rw)
{
    asm volatile(ARM64_LSE_ATOMIC_INSN(
    "    stlr    wzr, %0",
    "    swpl    wzr, wzr, %0")
    : "=Q" (rw->lock) :: "memory");
}

/*
 * arch_read_lock
 *
 * 读锁实现
 *
 * 它以独占方式加载锁值,递增锁值,如果是正值,并且CPU仍然独占地拥有该位置,则将新值存储回。如果值为负,则表示锁已被持有。
 *  
 * 在解锁过程中,可能有多个活动的读锁,但没有写锁。
 *
 * 在加载获取和存储释放指令中,内存屏障是隐式的。
 *
 * 注意,在未定义的情况下,例如两次解锁一个锁,LL/SC和LSE实现可能表现出不同的行为(尽管这对lockdep没有影响)。
 *
 *
 * 读取者进入临界区先要判断是否有write进程在临界区,如果有必须自旋。如果没有,则可以进入临界区
 */
static inline void arch_read_lock(arch_rwlock_t *rw)
{
    unsigned int tmp, tmp2;

    asm volatile(
    "    sevl\n"
    ARM64_LSE_ATOMIC_INSN(
    /* LL/SC */
    "1:    wfe\n"
    "2:    ldaxr    %w0, %2\n"                /* tmp = rw->lock 读取锁的值,赋值给tmp变量 */
    "    add    %w0, %w0, #1\n"                /* tmp += 1 */
    "    tbnz    %w0, #31, 1b\n"            /* 判断tmp[31]是否等于0,不等于0也就是说write进程在临界区,需要自旋等待,跳到标号1继续 */
    "    stxr    %w1, %w0, %2\n"            /* 将tmp的值复制给lock,然后将结果放入tmp2中 */
    "    cbnz    %w1, 2b\n"                /* 判断tmp2是否等于0,不等于0就跳到标号2继续 */
    __nops(1),
    /* LSE atomics */
    "1:    wfe\n"
    "2:    ldxr    %w0, %2\n"
    "    adds    %w1, %w0, #1\n"
    "    tbnz    %w1, #31, 1b\n"
    "    casa    %w0, %w1, %2\n"
    "    sbc    %w0, %w1, %w0\n"
    "    cbnz    %w0, 2b")
    : "=&r" (tmp), "=&r" (tmp2), "+Q" (rw->lock)
    :
    : "cc", "memory");
}

/*
 * arch_read_unlock
 *
 * 读取者退出临界区只需要将锁的值减1即可
 *
 */
static inline void arch_read_unlock(arch_rwlock_t *rw)
{
    unsigned int tmp, tmp2;

    asm volatile(ARM64_LSE_ATOMIC_INSN(
    /* LL/SC */
    "1:    ldxr    %w0, %2\n"            /* 读取锁的值,复制给tmp */
    "    sub    %w0, %w0, #1\n"            /* 将tmp的值减去1,同时将结果放入到tmp中 */
    "    stlxr    %w1, %w0, %2\n"        /* 将tmp的值复制给lock,然后将结果存放到tmp2 */
    "    cbnz    %w1, 1b",            /* 如果tmp2的值不为0,就跳转到标号1继续执行 */
    /* LSE atomics */
    "    movn    %w0, #0\n"
    "    staddl    %w0, %2\n"
    __nops(2))
    : "=&r" (tmp), "=&r" (tmp2), "+Q" (rw->lock)
    :
    : "memory");
}

猜你喜欢

转载自blog.csdn.net/xiaozhiwise/article/details/110631311