读写自旋锁
读写自旋锁(通常简称读写锁)是对自旋锁的改进,区分读者和写者,允许多个读者同时进入临界区,读者和写者互斥,写者和写者互斥。
如果读者占有读锁,写者申请写锁的时候自旋等待。如果写者占有写锁,读者申请读锁的时候自旋等待。
读写自旋锁的定义如下:
include/linux/rwlock_types.h
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, ARM64 架构的定义如下:
arch/arm64/include/asm/spinlock_types.h
typedef struct {
volatile unsigned int lock;
} arch_rwlock_t;
定义并且初始化静态读写自旋锁的方法如下:DEFINE_RWLOCK(x);
在运行时动态初始化读写自旋锁的方法如下:rwlock_init(lock);
申请读锁的函数如下。
(1) read_lock(lock)
申请读锁,如果写者占有写锁,当前处理器自旋等待。
(2) read_lock_bh(lock)
申请读锁,并且禁止当前处理器的软中断。
(3) read_lock_irq(lock)
申请读锁,并且禁止当前处理器的硬中断。
(4) read_lock_irqsave(lock, flags)
申请读锁,保存当前处理器的硬中断状态,并且禁止当前处理器的硬中断。
(5) read_trylock(lock)
尝试申请读锁,如果没有占有写锁的写者,那么申请读锁成功,返回 1;如果写者占有写锁,那么当前处理器不等待,立即返回 0。
释放读锁的函数如下。
(1) read_unlock(lock)
(2) read_unlock_bh(lock)
释放读锁,并且开启当前处理器的软中断。
(3) read_unlock_irq(lock)
释放读锁,并且开启当前处理器的硬中断。
(4) read_unlock_irqrestore(lock, flags)
释放读锁,并且恢复当前处理器的硬中断状态。
申请写锁的函数如下。
(1) write_lock(lock)
申请写锁,如果写者占有写锁或者读者占有读锁,当前处理器自旋等待。
(2) write_lock_bh(lock)
申请写锁,并且禁止当前处理器的软中断。
(3) write_lock_irq(lock)
申请写锁,并且禁止当前处理器的硬中断。
(4) write_lock_irqsave(lock, flags)
申请写锁,保存当前处理器的硬中断状态,并且禁止当前处理器的硬中断。
(5) write_trylock(lock)
尝试申请写锁,如果没有占有锁的写者和读者,那么申请写锁成功,返回 1;如果写者占有写锁或者读者占有读锁,那么当前处理器不等待,立即返回 0。
释放写锁的函数如下。
(1) write_unlock(lock)
(2) write_unlock_bh(lock)
释放写锁,并且开启当前处理器的软中断。
(3) write_unlock_irq(lock)
释放写锁,并且开启当前处理器的硬中断。
(4) write_unlock_irqrestore(lock, flags)
释放写锁,并且恢复当前处理器的硬中断状态。
读写自旋锁使用一个无符号 32 位整数作为计数值,写锁使用最高位,读锁使用其余31 位,算法如下。
(1)申请写锁时,如果计数值是 0,那么设置计数的最高位,进入临界区;如果计数值不是 0,说明写者占有写锁或者读者占有读锁,那么自旋等待。
(2)申请读锁时,如果计数值的最高位是 0,那么把计数加 1,进入临界区;如果计数的最高位不是 0,说明写者占有写锁,那么自旋等待。
(3)释放写锁时,把计数值设置为 0。
(4)释放读锁时,把计数值减 1。
读写自旋锁的缺点是:如果读者很多,写者很难获取写锁,可能饿死。假设有一个读者占有读锁,然后写者申请写锁,写者需要自旋等待,接着另一个读者申请读锁,它可以获取读锁,如果两个读者轮流占有读锁,可能造成写者饿死。
针对这个缺点,内核实现了排队读写锁,主要改进是:如果写者正在等待写锁,那么读者申请读锁时自旋等待,写者在锁被释放以后先得到写锁。排队读写锁的配置宏是 CONFIG_QUEUED_RWLOCKS,源文件是“ kernel/locking/qrwlock.c”(MT 平台目前没有打开此配置)。
include/asm-generic/qrwlock_types.h
……
typedef struct qrwlock {
atomic_t cnts;
arch_spinlock_t wait_lock;
} arch_rwlock_t;
……
include/asm-generic/qrwlock.h
……
#define arch_read_can_lock(l) queued_read_can_lock(l)
#define arch_write_can_lock(l) queued_write_can_lock(l)
#define arch_read_lock(l) queued_read_lock(l)
#define arch_write_lock(l) queued_write_lock(l)
#define arch_read_trylock(l) queued_read_trylock(l)
#define arch_write_trylock(l) queued_write_trylock(l)
#define arch_read_unlock(l) queued_read_unlock(l)
#define arch_write_unlock(l) queued_write_unlock(l)
……