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的实现非常简单,分别是关闭抢占和打开抢占:

  1. static inline void __rcu_read_lock(void)
  2. {
  3. preempt_disable();
  4. }
  5. static inline void __rcu_read_unlock(void)
  6. {
  7. preempt_enable();
  8. }

这时是否度过宽限期的判断就比较简单:每个CPU都经过一次抢占。因为发生抢占,就说明不在rcu_read_lock和rcu_read_unlock之间,必然已经完成访问或者还未开始访问。

1.2 每个CPU度过quiescnet state

接下来我们看每个CPU上报完成抢占的过程。kernel把这个完成抢占的状态称为quiescent state。每个CPU在时钟中断的处理函数中,都会判断当前CPU是否度过quiescent state。

  1. void update_process_times(int user_tick)
  2. {
  3. ......
  4. rcu_check_callbacks(cpu, user_tick);
  5. ......
  6. }
  7. void rcu_check_callbacks(int cpu, int user)
  8. {
  9. ......
  10. if (user || rcu_is_cpu_rrupt_from_idle()) {
  11. /*在用户态上下文,或者idle上下文,说明已经发生过抢占*/
  12. rcu_sched_qs(cpu);
  13. rcu_bh_qs(cpu);
  14. } else if (!in_softirq()) {
  15. /*仅仅针对使用rcu_read_lock_bh类型的rcu,不在softirq,
  16. *说明已经不在read_lock关键区域*/
  17. rcu_bh_qs(cpu);
  18. }
  19. rcu_preempt_check_callbacks(cpu);
  20. if (rcu_pending(cpu))
  21. invoke_rcu_core();
  22. ......
  23. }

这里补充一个细节说明,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”这个名字得来的缘由。

树结构每层的节点数量和叶子节点数量由一系列的宏定义来决定:

  1. #define MAX_RCU_LVLS 4
  2. #define RCU_FANOUT_1 (CONFIG_RCU_FANOUT_LEAF)
  3. #define RCU_FANOUT_2 (RCU_FANOUT_1 * CONFIG_RCU_FANOUT)
  4. #define RCU_FANOUT_3 (RCU_FANOUT_2 * CONFIG_RCU_FANOUT)
  5. #define RCU_FANOUT_4 (RCU_FANOUT_3 * CONFIG_RCU_FANOUT)
  6. #if NR_CPUS <= RCU_FANOUT_1
  7. # define RCU_NUM_LVLS 1
  8. # define NUM_RCU_LVL_0 1
  9. # define NUM_RCU_LVL_1 (NR_CPUS)
  10. # define NUM_RCU_LVL_2 0
  11. # define NUM_RCU_LVL_3 0
  12. # define NUM_RCU_LVL_4 0
  13. #elif NR_CPUS <= RCU_FANOUT_2
  14. # define RCU_NUM_LVLS 2
  15. # define NUM_RCU_LVL_0 1
  16. # define NUM_RCU_LVL_1 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_1)
  17. # define NUM_RCU_LVL_2 (NR_CPUS)
  18. # define NUM_RCU_LVL_3 0
  19. # define NUM_RCU_LVL_4 0
  20. #elif NR_CPUS <= RCU_FANOUT_3
  21. # define RCU_NUM_LVLS 3
  22. # define NUM_RCU_LVL_0 1
  23. # define NUM_RCU_LVL_1 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_2)
  24. # define NUM_RCU_LVL_2 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_1)
  25. # define NUM_RCU_LVL_3 (NR_CPUS)
  26. # define NUM_RCU_LVL_4 0
  27. #elif NR_CPUS <= RCU_FANOUT_4
  28. # define RCU_NUM_LVLS 4
  29. # define NUM_RCU_LVL_0 1
  30. # define NUM_RCU_LVL_1 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_3)
  31. # define NUM_RCU_LVL_2 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_2)
  32. # define NUM_RCU_LVL_3 DIV_ROUND_UP(NR_CPUS, RCU_FANOUT_1)
  33. # 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计数

  1. void __rcu_read_lock(void)
  2. {
  3. current->rcu_read_lock_nesting++;
  4. barrier(); /* critical section after entry code. */
  5. }
  6. void __rcu_read_unlock(void)
  7. {
  8. struct task_struct *t = current;
  9. if (t->rcu_read_lock_nesting != 1) {
  10. --t->rcu_read_lock_nesting;
  11. } else {
  12. barrier(); /* critical section before exit code. */
  13. t->rcu_read_lock_nesting = INT_MIN;
  14. barrier(); /* assign before ->rcu_read_unlock_special load */
  15. if (unlikely(ACCESS_ONCE(t->rcu_read_unlock_special)))
  16. rcu_read_unlock_special(t);
  17. barrier(); /* ->rcu_read_unlock_special load before assign */
  18. t->rcu_read_lock_nesting = 0;
  19. }
  20. }

当抢占发生时,__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原理分析的更多相关文章

  1. Linux内核[CVE-2016-5195] (dirty COW)原理分析

    [原创]Linux内核[CVE-2016-5195] (dirty COW)原理分析-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com https://bbs.pediy.com/ ...

  2. Linux内核同步 - RCU基础

    一.前言 关于RCU的文档包括两份,一份讲基本的原理(也就是本文了),一份讲linux kernel中的实现.第二章描述了为何有RCU这种同步机制,特别是在cpu core数目不断递增的今天,一个性能 ...

  3. Linux内核同步机制--转发自蜗窝科技

    Linux内核同步机制之(一):原子操作 http://www.wowotech.net/linux_kenrel/atomic.html 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个 ...

  4. Linux内核同步机制

    http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环 ...

  5. Linux内核同步

    Linux内核剖析 之 内核同步 主要内容 1.内核请求何时以交错(interleave)的方式执行以及交错程度如何. 2.内核所实现的基本同步机制. 3.通常情况下如何使用内核提供的同步机制. 内核 ...

  6. [内核同步]浅析Linux内核同步机制

    转自:http://blog.csdn.net/fzubbsc/article/details/37736683?utm_source=tuicool&utm_medium=referral ...

  7. Linux内核同步机制之(五):Read Write spin lock【转】

    一.为何会有rw spin lock? 在有了强大的spin lock之后,为何还会有rw spin lock呢?无他,仅仅是为了增加内核的并发,从而增加性能而已.spin lock严格的限制只有一个 ...

  8. Linux内核同步 - Read/Write spin lock

    一.为何会有rw spin lock? 在有了强大的spin lock之后,为何还会有rw spin lock呢?无他,仅仅是为了增加内核的并发,从而增加性能而已.spin lock严格的限制只有一个 ...

  9. Linux内核同步 - spin_lock

    一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机 ...

随机推荐

  1. Prototype 原型模式 复制 浅拷贝 clone MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  2. 错误: 找不到或无法加载主类 Files\red5-server ,原因与解决办法

    因为你把 red5放到了 Program Files 下,而Program Files 中间有个空格,启动路径不允许有空格,换个没空格的路径就OK啦

  3. Web页面切图和CSS注意事项

    一.Asp.net中的线程池设置 在Asp.net的服务处理中,每当服务器收到一个请求,HttpRuntime将从HttpApplication池中获取一个HttpApplication对象处理此请求 ...

  4. Web开发者不容错过的10段CSS代码

    Web开发技术每年都在革新,浏览器已逐渐支持CSS3特性,并且网站设计师和前端开发者普遍采用这种新技术进行设计与开发.但仍然有一些开发者迷恋着一些CSS2代码. 本文将分享20段非常专业的CSS2/C ...

  5. 浅谈Jquery中的bind()、live()、delegate()、on()绑定事件方式

    一.on(),live(),bind() on() 为指定的元素,添加一个或多个事件处理程序,并规定当这些事件发生时运行的函数.使用 on() 方法的事件处理程序适用于当前或未来的元素(比如由脚本创建 ...

  6. Android -- 打开本地图片且显示路径

    背景                                                                                          代码       ...

  7. IIS-设置session时间

    session会话类型

  8. 劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(八)

    [重构](续) 牌的表示: 一副牌有52张,可用一整数数组描述.但是由于在游戏过程中牌数在不断减少,所以用一表示剩余张数的整数和一整数数组共同描述.C99支持一种变量长度数组,但用在这里并没有什么特别 ...

  9. ZH奶酪:标准偏差

    标准偏差 标准偏差(Std Dev,Standard Deviation) -统计学名词.一种量度数据分布的分散程度之标准,用以衡量数据值偏离算术平均值的程度.标准偏差越小,这些值偏离平均值就越少,反 ...

  10. eclipse 关闭web项目无用校验

      eclipse 关闭web项目无用校验 CreateTime--2018年4月8日16:21:01 Author:Marydon 1.关闭javascript校验 1.1 切换到视图Navigat ...