【精通内核】Linux 内核 rcu 锁深度解析
前言
📫作者简介:小明java问道之路,专注于研究计算机底层/Java/Liunx 内核,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计📫
🏆 InfoQ 签约博主、CSDN 专家博主/Java 领域优质创作者/CSDN 内容合伙人、阿里云专家/签约博主、华为云专家、51CTO 专家/TOP 红人 🏆
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~
本文导读
我们在上一篇文章中学习了 Linux内核rcu锁实现原理与源码解析 ,提到了写任务通过 rcu_assign_pointer 来修改指针,通过 synchronize_kernel 来等待所有的读任务完成。而读任务通过 rcu_read_lock、rcu_read_unlock rcu_dereference 来上锁、解锁、获取引用值。
本篇我们看下这几个操作都做了什么。
一、rcu_read_lock()
读任务声明读端临界区开始,用于保证之后的对修改对象的访问都不会被回收(还记得上述说不就是垃圾回收吗?先创建一个副本(然后在合适时机删除副本),定义如下。
二、rcu_read_unlock()
表明读任务对对象的使用已经完成,也即结束临界区,度过了宽限期,定义如下。
三、synchronize_rcu() / call_rcu()
synchronize_rcu() 写任务等待所有读任务都度过了宽限期,然后执行回收垃圾对象操作。
这个函数阻塞了写任务,在某些场景下写任务不能被阻塞,那么怎么做呢?答案是调用 call_rcu 函数,这将不会阻塞写任务,会将 callback 函数挂到 RCU 回调列表中,等所有读任务都度过宽限期后调用 callback 释放 old 对象。
CPU0 在 1 处开启读临界区,CPU1 在 2 处开始等待所有读任务度过宽限期,这时 CPU2 在 3 处开启读临界区。注意,这时 CPU2 读到的是 CPU1 修改过后的数据,这是没问题的,因为 CPU2 是在 CPUI 修改后才访问的资源,而当在 4 处 CPUO 退出读临界区后,CPU1 将直接退出等待状态,即 CPU1 只需要等待 CPU0 而不需要等待 CPU2。所以,读者这里更加能够理解宽限期的概念了吧?只需要等待写任务写之前已经开始读资源的任务即可。
四、rcu_assign_pointer()
rcu_assign_pointer() 函数很简单,查看定义 typeof(p)rcu_assign_pointer(p,typeof(p)v);,有没有发赏传入两个指针,即需要修改的值的指针和修改后的值的指针,然后返回修改后的指针。实际上这是一个宏定义,如下所示。
也就是说,写任务通过 rcu_assign_pointer() 函数修改需要更新的指针,因为在不同 CPU 架构中可能会产生重排序,所以用这个宏定义来保证写顺序。
五、rcu_dereference()
和上述一样,也是个宏定义,实际上就是给读写任务用于获取指针,这只不过同样采用了内存屏障的方式保证了读顺序罢了。当然,这里的宏定义必须在读临界区内读取,否则是非法的。看以下代码
总结
上述便是 rcu 锁的核心概念,核心函数。Rcu 锁的难点不在于 API 的使用,而在于宽限期的概念,即如何判断所有任务已经度过宽限期呢?上述讨论了可以判断每个 CPU 否调因为发生了任务调度代表了在内核态的抢占过程,因为在读临界区内是禁止抢占的。
版权声明: 本文为 InfoQ 作者【小明Java问道之路】的原创文章。
原文链接:【http://xie.infoq.cn/article/8f1f18765dd3d2ad79130273b】。文章转载请联系作者。
评论