Linux顺序锁详解

顺序锁的特点

  • 允许任意多个读操作同时进入临界区
  • 只允许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);
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容