news 2026/9/25 20:10:26

Linux 锁 (4) - seqlock

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 锁 (4) - seqlock

文章目录

  • 1. 前言
  • 2. seqlock 实现
  • 3. 小结
  • 4. 参考资料

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. seqlock 实现

seqlock通过一个初始为 0 计数器,实现writer和reader共享数据的读写同步。

writer一方更新共享数据的步骤如下:

  1. 计数器 +1,计数变为奇数
  2. 更新共享数据
  3. 计数器再 +1,计数变为偶数

reader一方读取共享数据的步骤如下:

  1. 循环读取计数器的值,直到读到一个偶数值并返回,记为S1
  2. 读取共享数据
  3. 再次读取计数的值,直到读到一个偶数值并返回,计为S2
  4. 比较 S1 和 S2,如果S1 == S2,则表明步骤2.读取的共享数据是最新的,则可用;否则跳到步骤1.再次读取。

来看具体的实现代码:

// include/linux/seqlock.htypedefstructseqcount{unsignedsequence;#ifdefCONFIG_DEBUG_LOCK_ALLOC...#endif}seqcount_t;
  • seqlock 初始化
staticinlinevoid__seqcount_init(seqcount_t*s,constchar*name,structlock_class_key*key){/* * Make sure we are not reinitializing a held lock: */lockdep_init_map(&s->dep_map,name,key,0);s->sequence=0;}#defineseqcount_init(s)__seqcount_init(s,NULL,NULL)#defineSEQCNT_ZERO(lockname){.sequence=0,SEQCOUNT_DEP_MAP_INIT(lockname)}

初始化将seqcount::sequence设为0。

  • writer:更新共享数据

writer更新共享数据的逻辑如下:

structseqcountseqlock;seqcount_init(&seqlock);write_seqcount_begin(&seqlock);// 更新 writer/reader 贡献的数据write_seqcount_end(&seqlock);

seqcount_init()初始化了锁的计数器为0,这在前面已经分析,这里不再赘述。来看write_seqcount_begin()和write_seqcount_end()。

先看共享数据更新之前的write_seqcount_begin():

staticinlinevoidwrite_seqcount_begin(seqcount_t*s){write_seqcount_begin_nested(s,0);}staticinlinevoidwrite_seqcount_begin_nested(seqcount_t*s,intsubclass){raw_write_seqcount_begin(s);...}staticinlinevoidraw_write_seqcount_begin(seqcount_t*s){s->sequence++;/* 标记写开始: 奇数 *//* * 确保让系统中的所有 CPU 观察到: * s->sequence++ 操作, 在 锁保护的共享数据 的 更新操作 之前 完成. */smp_wmb();}

再看共享数据更新之后的write_seqcount_end():

staticinlinevoidwrite_seqcount_end(seqcount_t*s){...raw_write_seqcount_end(s);}staticinlinevoidraw_write_seqcount_end(seqcount_t*s){/* * 确保让系统中的所有 CPU 观察到: * s->sequence++ 操作, 在 锁保护的共享数据 的 更新操作 之后 完成. */smp_wmb();s->sequence++;/* 标记写结束: 偶数 */}
  • reader:读取(更新的)共享数据

reader读取共享数据的逻辑如下(假设使用的struct seqcount名为seqlock):

unsignedlongseq;do{seq=read_seqcount_begin(&seqlock);// 读取共享数据}while(read_seqcount_retry(&seqlock,seq));// 本次读取数据结束

先看read_seqcount_begin():

staticinlineunsignedread_seqcount_begin(constseqcount_t*s){...returnraw_read_seqcount_begin(s);}staticinlineunsignedraw_read_seqcount_begin(constseqcount_t*s){unsignedret=__read_seqcount_begin(s);/* * 确保系统中所有 CPU 观察到: * __read_seqcount_begin() 对锁计数器 s->sequence 的读取操作, * 在接下来 对共享数据的读操作 之前 完成. * 也即按 writer 先递增(第 1 次)了 锁计数器 s->sequence, 然后 * 更新共享数据同样的顺序去读取。 */smp_rmb();returnret;}staticinlineunsigned__read_seqcount_begin(constseqcount_t*s){unsignedret;repeat:ret=READ_ONCE(s->sequence);if(unlikely(ret&1)){/* 奇数: 正在写 */cpu_relax();gotorepeat;/* 重复读取计数器,直至写结束 */}returnret;/* 返回偶数计数器 */}

再看read_seqcount_retry():

staticinlineintread_seqcount_retry(constseqcount_t*s,unsignedstart){/* * 确保系统中所有 CPU 观察到: * __read_seqcount_retry() 对锁计数器 s->sequence 的读取操作, 在 对共享数据的读操作 * 之后 完成。 * 也即按 writer 先更新了共享数据,然后第 2 次递增了 锁计数器 s->sequence 同样的顺序 * 去读取。 */smp_rmb();return__read_seqcount_retry(s,start);}staticinlineint__read_seqcount_retry(constseqcount_t*s,unsignedstart){returnunlikely(s->sequence!=start);}

当然,上面reader的读取逻辑,在writer首次更新共享数据前,reader也能正确工作,因为初始的计数值为 0,是一个偶数。

3. 小结

seqlock适合保护快速读写的小量数据,比单单保护单个数据 atomic 操作更进一步。类似于rwlock,seqlock也是针对读多写少的场景,但它改善了rwlock的writer的优先级低,甚至极端情况下被饿死的问题,seqlock的writer优先级更高,它可以随时打断reader(通过更新计数器)。seqlock不会导致进程睡眠,但也不会像 spinlock 那样禁用抢占,一直在一个 CPU 上自旋,seqlock持锁期间可能出现临界区代码被调度出去的情形。如果seqlock保护的共享数据包含指针,则不宜使用 seqlock,因为 writer 可能会使 reader 正在访问的指针无效,因为 reader 工作的同时,它无法阻止 writer 对共享数据的更新。最后,seqlock无法串行化多个writer,必须借助外部锁来实现这一点。

4. 参考资料

Sequence counters and sequential locks

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/23 9:39:55

基于Docker与DDNSTO的Nas内网穿透Web服务实战指南

1. 为什么需要内网穿透? 很多朋友买了Nas后,发现只能在局域网内访问存储的文件和部署的服务,这就像买了一栋别墅却只能在后院活动一样浪费。想象一下这样的场景:你在公司想查看家里Nas上的文档,出差时想用手机访问家里…

作者头像 李华
网站建设 2026/8/23 9:39:55

最长公共子序列(LCS)——从零开始的动态规划

LCS最长公共子序列:从理解到实现,一次讲透在字符串和动态规划的学习中,LCS(Longest Common Subsequence,最长公共子序列)是一个绕不开的经典问题。很多人一开始觉得它和“最长公共子串”差不多,…

作者头像 李华
网站建设 2026/8/23 9:39:55

计算机毕业设计:Python豆瓣图书数据分析平台 Flask框架 可视化 爬虫 书籍 大数据 机器学习(建议收藏)✅

博主介绍:✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业项目实战6年之久,选择我们就是选择放心、选择安心毕业✌ > 🍅想要获取完整文章或者源码,或者代做,拉到文章底部即可与…

作者头像 李华
网站建设 2026/8/23 9:39:56

用HTML5 Canvas和原生JS手搓一个Emoji消消乐(附完整源码和算法解析)

用HTML5 Canvas和原生JS手搓一个Emoji消消乐(附完整源码和算法解析) 在移动游戏风靡的今天,消除类游戏因其简单易上手、又兼具策略性的特点,始终占据着一席之地。作为前端开发者,自己动手实现一个消除游戏不仅能巩固Ca…

作者头像 李华