写点什么

【精通内核】Linux 内核 rcu 锁深度解析

  • 2022 年 9 月 16 日
    上海
  • 本文字数:1601 字

    阅读完需:约 5 分钟

前言

📫作者简介小明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()

读任务声明读端临界区开始,用于保证之后的对修改对象的访问都不会被回收(还记得上述说不就是垃圾回收吗?先创建一个副本(然后在合适时机删除副本),定义如下。

#define rcu_read_lock()	preempt disable()
复制代码

二、rcu_read_unlock()

表明读任务对对象的使用已经完成,也即结束临界区,度过了宽限期,定义如下。

#define rcu read_unlock()	preempt_enable()
复制代码

三、synchronize_rcu() / call_rcu()

synchronize_rcu() 写任务等待所有读任务都度过了宽限期,然后执行回收垃圾对象操作。

这个函数阻塞了写任务,在某些场景下写任务不能被阻塞,那么怎么做呢?答案是调用 call_rcu 函数,这将不会阻塞写任务,会将 callback 函数挂到 RCU 回调列表中,等所有读任务都度过宽限期后调用 callback 释放 old 对象。

    CPU0    CPU 1   CPU 2	      1    rcu_read_lock()	2            enters synchronize_rcu()3                    rcu_read_lock()4    rcu_read unlock()5            exits synchronize_rcu()	6                    rcu_read_unlock()	
复制代码

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);,有没有发赏传入两个指针,即需要修改的值的指针和修改后的值的指针,然后返回修改后的指针。实际上这是一个宏定义,如下所示。

#define rcu_assign_pointer(p,v) ({    // 来个写屏障,保证写操作不会重排序,即后面的赋值操作不会重排序到这个屏障之前的写操作之前    // 之前的写也不会重排序到指针赋值之后    smp_wmb();    (p)=(v);})
复制代码

也就是说,写任务通过 rcu_assign_pointer() 函数修改需要更新的指针,因为在不同 CPU 架构中可能会产生重排序,所以用这个宏定义来保证写顺序。

五、rcu_dereference()

#define rcu dereference(p) {    typeof(p)_p1=p;    smp_read_barrier_depends();	//来个读屏障,保证读操作不会重排序	    (__p1);}
复制代码

和上述一样,也是个宏定义,实际上就是给读写任务用于获取指针,这只不过同样采用了内存屏障的方式保证了读顺序罢了。当然,这里的宏定义必须在读临界区内读取,否则是非法的。看以下代码

rcu_read lock();p = rcu_dereference(head.next);rcu read unlock(); x = p->address;  // 非法,此时可能p已经被释放了
rcu_read_lock(); y = p->data; // 非法,此时可能p已经被释放了rcu_read_unlock();
复制代码

总结

上述便是 rcu 锁的核心概念,核心函数。Rcu 锁的难点不在于 API 的使用,而在于宽限期的概念,即如何判断所有任务已经度过宽限期呢?上述讨论了可以判断每个 CPU 否调因为发生了任务调度代表了在内核态的抢占过程,因为在读临界区内是禁止抢占的。

发布于: 刚刚阅读数: 3
用户头像

InfoQ签约作者/技术专家/博客专家 2020.03.20 加入

🏆InfoQ签约作者、CSDN专家博主/Java领域优质创作者、阿里云专家/签约博主、华为云专家、51CTO专家/TOP红人 📫就职某大型金融互联网公司高级工程师 👍专注于研究Liunx内核、Java、源码、架构、设计模式、算法

评论

发布
暂无评论
【精通内核】Linux内核rcu锁深度解析_RCU_小明Java问道之路_InfoQ写作社区