顺序锁的特点
- 允许任意多个读操作同时进入临界区
- 只允许1个写操作同时进入临界区
- 如果当前只有读操作,则写操作可以随时进入临界区,不用理会读操作
顺序锁的用法
读操作主要有2个API:
read_seqbegin()
进入临界区
read_seqretry()
检测在读操作的过程中有没有写操作,如果有则需要重新读
写操作主要也有2个API
write_seqlock()
进入临界区
write_sequnlock()
离开临界区
示例用法如下:
// 读操作
unsigned long seq;
do {
// 进入临界区,此函数会返回当时的序列号
seq = read_seqbegin(&xxx_lock);
// 执行业务逻辑,读数据
...
// 这里的while判断是为了检查在我们读数据的过程中有没有write操作
// 比较根据传入的序列号和现在的序列号,如果不一致则说明有写操作
// read_seqretry()返回true,需要重试
// 否则完成读操作
} while (read_seqretry(&xtime_lock, seq));
// 写操作
write_seqlock(&xxx_lock);
// 写数据
...
// 离开临界区
write_sequnlock(&xxx_lock);
顺序锁的实现
顺序锁seq lock的定义
typedef struct {
struct seqcount seqcount; // 顺序计数器
spinlock_t lock; // 自旋锁
} seqlock_t;
seq lock实际上就是spin lock + sequence counter。
写操作的实现
// 进入临界区,顺序号+1
static inline void write_seqlock(seqlock_t *sl)
{
spin_lock(&sl->lock);
sl->sequence++;
}
// 离开临界区,顺序号再+1
// 没错,这里确实是再加1而不是减1
// 这样设计的理由是:
// 序号初始化为0,一个写操作正好把序号加2,确保是一个偶数
// 这样就有如下结论:
// 如果序号是偶数说明没有写操作
// 如果序号是奇数说明正在写操作
static inline void write_sequnlock(seqlock_t *sl)
{
s->sequence++;
spin_unlock(&sl->lock);
}
读操作:read_seqbegin
static inline unsigned read_seqbegin(const seqlock_t *sl)
{
unsigned ret;
repeat:
// 获取当前的序列号,如果序列号是奇数则说明有人正在写操作
// 需要重新获取,直到序列号是一个偶数
ret = ACCESS_ONCE(sl->sequence);
if (unlikely(ret & 1)) {
goto repeat;
}
return ret;
}
读操作:read_seqretry
static inline unsigned read_seqretry(const seqlock_t *sl, unsigned start)
{
// 如果传入的序列号start和现在的序列号sl->sequence不相等
// 则说明有人执行了写操作,需要重试
return unlikely(sl->sequence != start);
}