Linux内核同步 - RCU synchronize原理分析
RCU(Read-Copy Update)是Linux内核比较成熟的新型读写锁,具有较高的读写并发性能,常常用在需要互斥的性能关键路径。在kernel中,rcu有tiny rcu和tree rcu两种实现,tiny rcu更加简洁,通常用在小型嵌入式系统中,tree rcu则被广泛使用在了server, desktop以及android系统中。本文将以tree rcu为分析对象。
1 如何度过宽限期
RCU的核心理念是读者访问的同时,写者可以更新访问对象的副本,但写者需要等待所有读者完成访问之后,才能删除老对象。这个过程实现的关键和难点就在于如何判断所有的读者已经完成访问。通常把写者开始更新,到所有读者完成访问这段时间叫做宽限期(Grace Period)。内核中实现宽限期等待的函数是synchronize_rcu。
1.1 读者锁的标记
在普通的TREE RCU实现中,rcu_read_lock和rcu_read_unlock的实现非常简单,分别是关闭抢占和打开抢占:
- static inline void __rcu_read_lock(void)
- {
- preempt_disable();
- }
- static inline void __rcu_read_unlock(void)
- {
- preempt_enable();
- }
这时是否度过宽限期的判断就比较简单:每个CPU都经过一次抢占。因为发生抢占,就说明不在rcu_read_lock和rcu_read_unlock之间,必然已经完成访问或者还未开始访问。
1.2 每个CPU度过quiescnet state
接下来我们看每个CPU上报完成抢占的过程。kernel把这个完成抢占的状态称为quiescent state。每个CPU在时钟中断的处理函数中,都会判断当前CPU是否度过quiescent state。
- void update_process_times(int user_tick)
- {
- ......
- rcu_check_callbacks(cpu, user_tick);
- ......
- }
- void rcu_check_callbacks(int cpu, int user)
- {
- ......
- if (user || rcu_is_cpu_rrupt_from_idle()) {
- /*在用户态上下文,或者idle上下文,说明已经发生过抢占*/
- rcu_sched_qs(cpu);
- rcu_bh_qs(cpu);
- } else if (!in_softirq()) {
- /*仅仅针对使用rcu_read_lock_bh类型的rcu,不在softirq,
- *说明已经不在read_lock关键区域*/
- rcu_bh_qs(cpu);
- }
- rcu_preempt_check_callbacks(cpu);
- if (rcu_pending(cpu))
- invoke_rcu_core();
- ......
- }
这里补充一个细节说明,Tree RCU有多个类型的RCU State,用于不同的RCU场景,包括rcu_sched_state、rcu_bh_state和rcu_preempt_state。不同的场景使用不同的RCU API,度过宽限期的方式就有所区别。例如上面代码中的rcu_sched_qs和rcu_bh_qs,就是为了标记不同的state度过quiescent state。普通的RCU例如内核线程、系统调用等场景,使用rcu_read_lock或者rcu_read_lock_sched,他们的实现是一样的;软中断上下文则可以使用rcu_read_lock_bh,使得宽限期更快度过。
细分这些场景是为了提高RCU的效率。rcu_preempt_state将在下文进行说明。
1.3 汇报宽限期度过
每个CPU度过quiescent state之后,需要向上汇报直至所有CPU完成quiescent state,从而标识宽限期的完成,这个汇报过程在软中断RCU_SOFTIRQ中完成。软中断的唤醒则是在上述的时钟中断中进行。
update_process_times
-> rcu_check_callbacks
-> invoke_rcu_core
RCU_SOFTIRQ软中断处理的汇报流程如下:
rcu_process_callbacks
-> __rcu_process_callbacks
-> rcu_check_quiescent_state
-> rcu_report_qs_rdp
-> rcu_report_qs_rnp
其中rcu_report_qs_rnp是从叶子节点向根节点的遍历过程,同一个节点的子节点都通过quiescent state后,该节点也设置为通过。
这个树状的汇报过程,也就是“Tree RCU”这个名字得来的缘由。
树结构每层的节点数量和叶子节点数量由一系列的宏定义来决定:
- #define MAX_RCU_LVLS 4
- #define RCU_FANOUT_1 (CONFIG_RCU_FANOUT_LEAF)
- #define RCU_FANOUT_2 (RCU_FANOUT_1 * CONFIG_RCU_FANOUT)
- #define RCU_FANOUT_3 (RCU_FANOUT_2 * CONFIG_RCU_FANOUT)
- #define RCU_FANOUT_4 (RCU_FANOUT_3 * CONFIG_RCU_FANOUT)
- #if NR_CPUS <= RCU_FANOUT_1
- # define RCU_NUM_LVLS 1
- # define NUM_RCU_LVL_0 1
- # define NUM_RCU_LVL_1 (NR_CPUS)
- # define NUM_RCU_LVL_2 0
- # define NUM_RCU_LVL_3 0
- # define NUM_RCU_LVL_4 0
- #elif NR_CPUS <= RCU_FANOUT_2
- # define RCU_NUM_LVLS 2
- # define NUM_RCU_LVL_0 1
- # define NUM_RCU_LVL_1 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_1)
- # define NUM_RCU_LVL_2 (NR_CPUS)
- # define NUM_RCU_LVL_3 0
- # define NUM_RCU_LVL_4 0
- #elif NR_CPUS <= RCU_FANOUT_3
- # define RCU_NUM_LVLS 3
- # define NUM_RCU_LVL_0 1
- # define NUM_RCU_LVL_1 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_2)
- # define NUM_RCU_LVL_2 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_1)
- # define NUM_RCU_LVL_3 (NR_CPUS)
- # define NUM_RCU_LVL_4 0
- #elif NR_CPUS <= RCU_FANOUT_4
- # define RCU_NUM_LVLS 4
- # define NUM_RCU_LVL_0 1
- # define NUM_RCU_LVL_1 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_3)
- # define NUM_RCU_LVL_2 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_2)
- # define NUM_RCU_LVL_3 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_1)
- # define NUM_RCU_LVL_4 (NR_CPUS)
1.3 宽限期的发起与完成
所有宽限期的发起和完成都是由同一个内核线程rcu_gp_kthread来完成。通过判断rsp->gp_flags & RCU_GP_FLAG_INIT来决定是否发起一个gp;通过判断! (rnp->qsmask) && !rcu_preempt_blocked_readers_cgp(rnp))来决定是否结束一个gp。
发起一个GP时,rsp->gpnum++;结束一个GP时,rsp->completed = rsp->gpnum。
1.4 rcu callbacks处理
rcu的callback通常是在sychronize_rcu中添加的wakeme_after_rcu,也就是唤醒synchronize_rcu的进程,它正在等待GP的结束。
callbacks的处理同样在软中断RCU_SOFTIRQ中完成
rcu_process_callbacks
-> __rcu_process_callbacks
-> invoke_rcu_callbacks
-> rcu_do_batch
-> __rcu_reclaim
这里RCU的callbacks链表采用了一种分段链表的方式,整个callback链表,根据具体GP结束的时间,分成若干段:nxtlist -- *nxttail[RCU_DONE_TAIL] -- *nxttail[RCU_WAIT_TAIL] -- *nxttail[RCU_NEXT_READY_TAIL] -- *nxttail[RCU_NEXT_TAIL]。
rcu_do_batch只处理nxtlist -- *nxttail[RCU_DONE_TAIL]之间的callbacks。每个GP结束都会重新调整callback所处的段位,每个新的callback将会添加在末尾,也就是*nxttail[RCU_NEXT_TAIL]。
2 可抢占的RCU
如果config文件定义了CONFIG_TREE_PREEMPT_RCU=y,那么sychronize_rcu将默认使用rcu_preempt_state。这类rcu的特点就在于read_lock期间是允许其它进程抢占的,因此它判断宽限期度过的方法就不太一样。
从rcu_read_lock和rcu_read_unlock的定义就可以知道,TREE_PREEMPT_RCU并不是以简单的经过抢占为CPU渡过GP的标准,而是有个rcu_read_lock_nesting计数
- void __rcu_read_lock(void)
- {
- current->rcu_read_lock_nesting++;
- barrier(); /* critical section after entry code. */
- }
- void __rcu_read_unlock(void)
- {
- struct task_struct *t = current;
- if (t->rcu_read_lock_nesting != 1) {
- --t->rcu_read_lock_nesting;
- } else {
- barrier(); /* critical section before exit code. */
- t->rcu_read_lock_nesting = INT_MIN;
- barrier(); /* assign before ->rcu_read_unlock_special load */
- if (unlikely(ACCESS_ONCE(t->rcu_read_unlock_special)))
- rcu_read_unlock_special(t);
- barrier(); /* ->rcu_read_unlock_special load before assign */
- t->rcu_read_lock_nesting = 0;
- }
- }
当抢占发生时,__schedule函数会调用rcu_note_context_switch来通知RCU更新状态,如果当前CPU处于rcu_read_lock状态,当前进程将会放入rnp->blkd_tasks阻塞队列,并呈现在rnp->gp_tasks链表中。
从上文1.3节宽限期的结束处理过程我们可以知道,rcu_gp_kthread会判断! (rnp->qsmask) && !rcu_preempt_blocked_readers_cgp(rnp))两个条件来决定GP是否完成,其中!rnp->qsmask代表每个CPU都经过一次quiescent state,quiescent state的定义与传统RCU一致;!rcu_preempt_blocked_readers_cgp(rnp)这个条件就代表了rcu是否还有阻塞的进程。
Linux内核同步 - RCU synchronize原理分析的更多相关文章
- Linux内核[CVE-2016-5195] (dirty COW)原理分析
[原创]Linux内核[CVE-2016-5195] (dirty COW)原理分析-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com https://bbs.pediy.com/ ...
- Linux内核同步 - RCU基础
一.前言 关于RCU的文档包括两份,一份讲基本的原理(也就是本文了),一份讲linux kernel中的实现.第二章描述了为何有RCU这种同步机制,特别是在cpu core数目不断递增的今天,一个性能 ...
- Linux内核同步机制--转发自蜗窝科技
Linux内核同步机制之(一):原子操作 http://www.wowotech.net/linux_kenrel/atomic.html 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个 ...
- Linux内核同步机制
http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环 ...
- Linux内核同步
Linux内核剖析 之 内核同步 主要内容 1.内核请求何时以交错(interleave)的方式执行以及交错程度如何. 2.内核所实现的基本同步机制. 3.通常情况下如何使用内核提供的同步机制. 内核 ...
- [内核同步]浅析Linux内核同步机制
转自:http://blog.csdn.net/fzubbsc/article/details/37736683?utm_source=tuicool&utm_medium=referral ...
- Linux内核同步机制之(五):Read Write spin lock【转】
一.为何会有rw spin lock? 在有了强大的spin lock之后,为何还会有rw spin lock呢?无他,仅仅是为了增加内核的并发,从而增加性能而已.spin lock严格的限制只有一个 ...
- Linux内核同步 - Read/Write spin lock
一.为何会有rw spin lock? 在有了强大的spin lock之后,为何还会有rw spin lock呢?无他,仅仅是为了增加内核的并发,从而增加性能而已.spin lock严格的限制只有一个 ...
- Linux内核同步 - spin_lock
一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机 ...
随机推荐
- Android音乐播放-MediaPlayer
当你坐公交无聊的时候,当你淹没在地铁中人潮中的时候,你是否想内心保持一份的安静呢,那么请带上耳机,打开你的音乐播放器,听一首老歌带你进入宁静的世界,音乐播放这个功能在智能手机出现之前,诺基亚时代,甚至 ...
- 【Gson】简介 文档 基本使用 示例
简介 new TypeToken<List<Person>>() {}.getType() 1 1 1 new TypeToken<List<Person> ...
- IntPtr 转 string
假设有 intPtr pBuffer 方法一: 直接使用Marshal.PtrToStringAnsi方法: string ss = Marshal.PtrToStringAnsi(pBuffer); ...
- 【BZOJ2662】【BeiJing wc2012】冻结 分层图 裸的!
我都不好意思发题解了,看这篇博吧.(飞行路线的,基本一样) http://blog.csdn.net/vmurder/article/details/40075989 同学做了好久.我害怕题里有坑,又 ...
- BFC是什么
BFC 已经是一个耳听熟闻的词语了,网上有许多关于 BFC 的文章,介绍了如何触发 BFC 以及 BFC 的一些用处(如清浮动,防止 margin 重叠等).虽然我知道如何利用 BFC 解决这些问题, ...
- uva 701 - The Archeologists' Dilemma
题目链接:uva 701 - The Archeologists' Dilemma 题目大意:给出x,求一个e,使得x * 10 ^ y ≤ 2 ^ e < (x + 1) * 10 ^ y. ...
- 线性表 顺序存储 链式存储 ---java实现
首先抽象出一个线性表抽象类(包括主要的增删操作) public abstract class MyAbstractList<E> { public abstract void add(E ...
- OpenCV图像处理篇之阈值操作函数
阈值操作类型 这5种阈值操作类型保留opencv tutorials中的英文名称.依次为: Threshold Binary:即二值化,将大于阈值的灰度值设为最大灰度值.小于阈值的值设为0. Thre ...
- Wide - Residual - Inception Networks for R eal - time O bject D etection
本文的提出了一个WR-inception网络结构.只需更小的内存消耗和更小的计算量. 作者使用了一种微结构,整个宏观网络都是由这个微结构组成. 微结构 微结构的种类如下: 基本的残差结构(3*3,3* ...
- vue inheritAttrs、$attrs和$listeners使用
inheritAttrs.$attrs和$listeners使用场景: 组件传值,尤其是祖孙组件有跨度的传值. (1)inheritAttrs 属性说明:https://cn.vuejs.org/v2 ...