/*
* 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");
}