以下分析基于Linux kernel 3.10
读写信号量的原理
读写信号量的特点是:
1. 同一时刻最多有一个写者(writer)获得锁;
2. 同一时刻可以有多个读者(reader)获得锁;
3. 同一时刻写者和读者不能同时获得锁;
由于读者可以同时获得锁,因此提高了系统的并发程度,进而提高了系统的性能。
下图用状态转换图表示了在调用不同API时锁的相应状态。需要注意的是,锁的状态其实可以直接从读者有锁跳到写者有锁,例如有写者正sleep在down_write,而最后一个读者调用了up_read(),这时写者直接获得锁。
读写信号量的定义和API
参见文件include/linux/rwsem.h, 下面是读写信号量的定义和常用API。
struct rw_semaphore {
long count;
raw_spinlock_t wait_lock;
struct list_head wait_list;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
#define init_rwsem(sem) \
do { \
static struct lock_class_key __key; \
\
__init_rwsem((sem), #sem, &__key); \
} while (0)
extern void down_read(struct rw_semaphore *sem);
extern int down_read_trylock(struct rw_semaphore *sem);
extern void down_write(struct rw_semaphore *sem);
extern int down_write_trylock(struct rw_semaphore *sem);
extern void downgrade_write(struct rw_semaphore *sem);
extern void up_read(struct rw_semaphore *sem);
extern void up_write(struct rw_semaphore *sem);
从定义可以看出读写锁包含一个链表wait_list,它包含了所有等待该锁的task。wait_lock用于在多CPU情况下保护该链表。
1. 高16bits = 取补码(当前锁是否被写者获取() + 当前锁的等待链表非空())。如果当前锁被写者获取且等待链表非空,则高16bits应当等于(1+1)的补码,即0xfffe。如果链表为空,则高32bits等于(1+0)的补码,即0xffff。
2. 低16bits = 所有获取锁的写者加上获取锁的读者。如果有三个读者获取了锁,则低32bits等于3。