读写自旋锁
读写自旋锁(通常简称读写锁)是对自旋锁的改进,区分读者和写者,允许多个读者同时进入临界区,读者和写者互斥,写者和写者互斥。
如果读者占有读锁,写者申请写锁的时候自旋等待。如果写者占有写锁,读者申请读锁的时候自旋等待。
读写自旋锁的定义:
include/linux/rwlock_types.h
typedef struct {
arch_rwlock_t raw_lock;
...
} rwlock_t;
各种处理器架构需要自定义数据类型arch_rwlock_t,ARM64架构的定义:
typedef struct qrwlock {
union {
atomic_t cnts;
struct {
#ifdef __LITTLE_ENDIAN
u8 wlocked; /* Locked for write? */
u8 __lstate[3];
#else
u8 __lstate[3];
u8 wlocked; /* Locked for write? */
#endif
};
};
arch_spinlock_t wait_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)尝试申请读锁,如果没有占有写锁的写者,那么申请成功,返回1;如果写者占有写锁,那么当前处理器不等待,返回0
read_trylock(lock)
释放读锁的函数:
(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)尝试申请写锁,如果没有占有锁的写者和读者,那么申请写锁成功,返回1;如果写者占有写锁或者读者占有读锁,那么当前处理器不等待,立即返回0
write_trylock(lock)
释放写锁的函数:
(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”。