顺序锁seqlock

简介: 顺序锁seqlock

顺序锁的设计思想是,对某一共享数据读取时不加锁,写的时候加锁。为了保证读取的过程中不会因为写入名的出现导致该共享数据的更新,需要在读取者和写入者之间引入一整型变量,称为顺序值sequence。读取者在开始读取前读取该sequence,在读取后再重读该值,如果与之前读取到的值不一致,则说明本次读取作过程中发生了数据更新,读取操作无效,因此要求写入者在开始写入的时候要更新sequence的值。

typedef struct {
    struct seqcount seqcount;
    spinlock_t lock;
} seqlock_t;

无符号型整数sequence用来协调读取者与写入者的作spinlock变量lock在多个写入者之间做互斥使用。

程序中如果想静态定义一个seqlock并同时初始化,可以使用DEFINESE_QLOCK宏,该宏会定义一个seqlock_t型变量并初始化其sequence为0,lock为0:

#define __SEQLOCK_UNLOCKED(lockname)            \
    {                        \
        .seqcount = SEQCNT_ZERO(lockname),    \
        .lock =    __SPIN_LOCK_UNLOCKED(lockname)    \
    }
#define DEFINE_SEQLOCK(x) \
        seqlock_t x = __SEQLOCK_UNLOCKED(x)

如果要动态初始化一个seqlock变量,可以使用seqlock_init:

#define seqlock_init(x)                    
do {                        
seqcount_init(&(x)->seqcount);        
spin_lock_init(&(x)->lock);        
} while (0)

下面看看写入者在seqlock上的上锁操作write_seqlock:

static inline void write_seqlock(seqlock_t *sl)
{
    spin_lock(&sl->lock);
    write_seqcount_begin(&sl->seqcount);
}
static inline void write_seqcount_begin(seqcount_t *s)
{
    write_seqcount_begin_nested(s, 0);
}
static inline void write_seqcount_begin_nested(seqcount_t *s, int subclass)
{
    raw_write_seqcount_begin(s);
    seqcount_acquire(&s->dep_map, subclass, 0, _RET_IP_);
}
static inline void raw_write_seqcount_begin(seqcount_t *s)
{
    s->sequence++;
        smp_wmb();
}

写入者在对写之前需要先获得seqlock上的自旋锁lock,这说明在写入者之间必须保证互斥操作,如果某一写入者成功获得lock。那么需要史新sequence的值以便让其他写入者知道共享数据发生了更新。写入者与写入者之间并不需要sequence。

static inline void write_sequnlock(seqlock_t *sl)
{
    write_seqcount_end(&sl->seqcount);
    spin_unlock(&sl->lock);
}
static inline void write_seqcount_end(seqcount_t *s)
{
    seqcount_release(&s->dep_map, 1, _RET_IP_);
    raw_write_seqcount_end(s);
}
static inline void raw_write_seqcount_end(seqcount_t *s)
{
    smp_wmb();
    s->sequence++;
}

主要的工作是释放自旋锁lock,至于写入者对sequence的更新,主要是用来告诉读取者有数据更新发生,所以必须确保sequence的值在写入的前后发生变化。在此基础上sequence提供的另外一个信息是写入过程有没有结束,这是用sequence的最低位来完成的,如果sequence & 0为0表明写入过程己经结束,否则表明写入过程正在进行·接下来会在读取者的seqlock作数中看到sequence的这两种用途。

某一写入者可以使用write_tryseqlock来保证在无法获得lock时不让自己进入自旋状态(当然也就无法更新数据)而直接返回0,成功获得锁则返回1:

读取者在读取开始前需要先调用read_seqbegin函数,该函数主要用来返回读取开始之前的sequence值:

static inline unsigned read_seqbegin(const seqlock_t *sl)
{
    return read_seqcount_begin(&sl->seqcount);
}
static inline unsigned read_seqcount_begin(const seqcount_t *s)
{
    seqcount_lockdep_reader_access(s);
    return raw_read_seqcount_begin(s);
}
static inline unsigned raw_read_seqcount_begin(const seqcount_t *s)
{
    unsigned ret = __read_seqcount_begin(s);
    smp_rmb();
    return ret;
}

s

tatic inline unsigned __read_seqcount_begin(const seqcount_t *s)
{
    unsigned ret;
repeat:
    ret = READ_ONCE(s->sequence);
    if (unlikely(ret & 1)) {
        cpu_relax();
        goto repeat;
    }
    return ret;
}

从函数的实现也可以看出,如果当前正好有写入者在进行写操作,那么该函数将不停循环直到写过程结束,前面曾提到sequence最低位的用途,这里正好是其实际使用的地方。另一方面,从读取者对写入过程结束的孤环等待可以看出,写入者的实际写入操作占用的时间不应太长。

内核还给读取者提供了一个read_seqretry函数,与read_seqbegin的返回值一起使用,来判定本次的读取操作是否有效:

static inline unsigned read_seqretry(const seqlock_t *sl, unsigned start)
{
    return read_seqcount_retry(&sl->seqcount, start);
}
static inline int read_seqcount_retry(const seqcount_t *s, unsigned start)
{
    smp_rmb();
    return __read_seqcount_retry(s, start);
}
static inline int __read_seqcount_retry(const seqcount_t *s, unsigned start)
{
    return unlikely(s->sequence != start);
}

函数的参数是读取者在读取操作之前调用read_seqbegin获得的初始值。如果本次读取无效(读取过程中发生了数据更新),那么readq返回1,否则返回0。下面分别给出写入者和读取者利用上面介绍的seqlock函数进行数据读/写协调的例子:

//定义一个全局的seqlock变量demo_seqlock
DEFINE_SEQLOCK(demo_seqlock);
//对于写入者的代码
...
//实际写之前调用write_seqlock获取自旋锁,同时更新sequence的值
write_seqlock(&demo_seqlock);
//获得自旋锁之后调用do_write进行实际的写入操作
do_write();
//写入结束,调用write_sequnlock释放锁
write_sequnlock(&demo_seqlock);
//对于读取者的代码
unsigned start;
do{
    //读操作前鲜活的sequence的值start,用以在读操作结束时判断数据是否发生更新
    //注意读操作无需获得锁
    start = read_seqbegin(&demo_seqlock);
    //调用do_read进行实际操作
    do_read();
}while(read_seqretry(&demo_seqlock,start));//如果数据有更新,再重新读取

如果考虑到中断安全的问题,可以使用读取者与写入者的对应版本:

void write_seqlock_irq(seqlock_t *sl)
void write_seqlock_bh(seqlock_t *sl)
write_seqlock_irqsave(lock, flags)
void read_seqlock_irq(seqlock_t *sl)
void read_seqlock_bh(seqlock_t *sl)
read_seqlock_irqsave(lock, flags

前面曾讨论过读取者与写入者自旋锁rwlock,对比这里的seqlock.会发现两者非常相似。不同之处在于seqlock在写的时候只与其他写入者互斥,而rwlock在写的时候与读取者和写入者都互斥·因此当要保护的资源很小很简单,会很频繁被访问并且写入操作很少发生且必须快速时,就可以使用seqlock。


目录
相关文章
|
存储 Cloud Native API
oss云网关配置
配置阿里云OSS与云网关实现灵活数据传输和访问控制。步骤包括开通OSS服务,创建Bucket,获取访问凭证,可选配置CORS和生命周期规则。云网关配置涉及阿里云云原生网关的代理规则设定或使用云存储网关集成OSS访问,具体配置需参照产品文档,因产品更新可能会有变动。
703 1
|
前端开发 JavaScript 定位技术
GIS前端编程-Leaflet插件扩展
GIS前端编程-Leaflet插件扩展
318 0
|
Web App开发
安利一款神奇——教你轻松下载百度网盘超大文件
安利一款神奇——教你轻松下载百度网盘超大文件
987 0
安利一款神奇——教你轻松下载百度网盘超大文件
|
安全 物联网 量子技术
量子加密技术
量子加密技术
915 1
|
监控 安全 网络安全
如何使用PortTunnel端口映射工具?
【10月更文挑战第8天】PortTunnel是一种端口映射工具,它允许用户将本地计算机上的端口映射到远程服务器上。要使用PortTunnel,您需要首先下载并安装该软件,然后按照以下步骤进行操作:,1. 打开PortTunnel并配置您的本地和远程端口设置。,2. 在“本地地址”字段中输入您要映射的本地IP地址。,3. 在“远程地址”字段中输入远程服务器的IP地址。,4. 在“本地端口”字段中输入您要映射的本地端口号。,5. 在“远程端口”字段中输入远程服务器上的端口号。,6. 单击“启动”按钮以开始映射过程
1765 2
|
搜索推荐 前端开发 算法
基于用户画像及协同过滤算法的音乐推荐系统,采用Django框架、bootstrap前端,MySQL数据库
本文介绍了一个基于用户画像和协同过滤算法的音乐推荐系统,使用Django框架、Bootstrap前端和MySQL数据库构建,旨在为用户提供个性化的音乐推荐服务,提高推荐准确性和用户满意度。
960 7
基于用户画像及协同过滤算法的音乐推荐系统,采用Django框架、bootstrap前端,MySQL数据库
|
人工智能 程序员 API
【AI大模型应用开发】1.0 Prompt Engineering(提示词工程)- 典型构成、原则与技巧,代码中加入Prompt
【AI大模型应用开发】1.0 Prompt Engineering(提示词工程)- 典型构成、原则与技巧,代码中加入Prompt
718 0
|
自然语言处理 PyTorch 语音技术
Transformers 4.37 中文文档(七十七)(1)
Transformers 4.37 中文文档(七十七)
464 0
|
自然语言处理
分享开源项目Chatwoot
Chatwoot是一个开源的客户支持平台,它提供了一系列工具,使企业能够通过各种渠道(如电子邮件、社交媒体、网站聊天窗口等)与客户进行沟通。它的目标是为企业提供一个统一的界面,汇总来自不同渠道的客户对话,从而提高客户服务效率和效果。
479 0
|
自然语言处理 文字识别 Linux
ModelScope运行语音合成模型的官网代码示例报错如何解决
ModelScope模型报错是指在使用ModelScope平台进行模型训练或部署时遇到的错误和问题;本合集将收集ModelScope模型报错的常见情况和排查方法,帮助用户快速定位问题并采取有效措施。
552 0