Linux 锁 (4) - seqlock
文章目录1. 前言2. seqlock 实现3. 小结4. 参考资料1. 前言限于作者能力水平本文可能存在谬误因此而给读者带来的损失作者不做任何承诺。2. seqlock 实现seqlock通过一个初始为 0 计数器实现writer和reader共享数据的读写同步。writer一方更新共享数据的步骤如下计数器 1计数变为奇数更新共享数据计数器再 1计数变为偶数reader一方读取共享数据的步骤如下循环读取计数器的值直到读到一个偶数值并返回记为S1读取共享数据再次读取计数的值直到读到一个偶数值并返回计为S2比较 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-sequence0;}#defineseqcount_init(s)__seqcount_init(s,NULL,NULL)#defineSEQCNT_ZERO(lockname){.sequence0,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名为seqlockunsignedlongseq;do{seqread_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:retREAD_ONCE(s-sequence);if(unlikely(ret1)){/* 奇数: 正在写 */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 操作更进一步。类似于rwlockseqlock也是针对读多写少的场景但它改善了rwlock的writer的优先级低甚至极端情况下被饿死的问题seqlock的writer优先级更高它可以随时打断reader通过更新计数器。seqlock不会导致进程睡眠但也不会像 spinlock 那样禁用抢占一直在一个 CPU 上自旋seqlock持锁期间可能出现临界区代码被调度出去的情形。如果seqlock保护的共享数据包含指针则不宜使用 seqlock因为 writer 可能会使 reader 正在访问的指针无效因为 reader 工作的同时它无法阻止 writer 对共享数据的更新。最后seqlock无法串行化多个writer必须借助外部锁来实现这一点。4. 参考资料Sequence counters and sequential locks
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2438380.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!