深入理解RCU | RCU源码剖析

434次阅读  |  发布于3年以前

hi, 上次分析了RCU核心思想:[深入理解RCU|核心原理] ,后面说会分享一篇RCU的源码剖析,其实我这边已经总结得差不多:

但自己思考了一下,发现大部分都是代码分析,这样很多人其实并不喜欢看源代码分析(代码有点多),所以可能其他方式更好,比如图解,我发现已经有人搞了这个,而且质量也挺高的,打算分享给大家。

背景

说明:

  1. Kernel版本:4.14
  2. ARM64处理器,Contex-A53,双核
  3. 使用工具:Source Insight 3.5, Visio

1 . 概述

我会假设你已经看过了``

[深入理解RCU|核心原理]

本文将进一步去探索下RCU背后的机制。

2 . 基础概念

2.1 Grace Period

来一张图:

2.2 Quiescent Status

Quiescent Status,用于描述处理器的执行状态。当某个CPU正在访问RCU保护的临界区时,认为是活动的状态,而当它离开了临界区后,则认为它是静止的状态。当所有的CPU都至少经历过一次QS后,宽限期将结束并触发回收工作。

3 . 数据结构

图来了:

看到这种描述,如果还是在懵逼的状态,那么再来一张拓扑图,让真相更白一点:

关键点来了:Tree RCU使用rcu_node节点来构造层级结构,进而管理静止状态Quiescent State和宽限期Grace Period,静止状态信息QS是从每个CPU的rcu_data往上传递到根节点的,而宽限期GP信息是通过根节点从上往下传递的,当每个CPU经历过一次QS状态后,宽限期结束

关键字段还是有必要介绍一下的,否则岂不是耍流氓?

struct rcu_state {
    struct rcu_node node[NUM_RCU_NODES];        // rcu_node节点数组,组织成层级树状
    struct rcu_node *level[RCU_NUM_LVLS + 1];   //指向每层的首个rcu_node节点,数组加1是为了消除编译告警
    struct rcu_data __percpu *rda;                      //指向每个CPU的rcu_data实例
    call_rcu_func_t call;                                   //指向特定RCU类型的call_rcu函数:call_rcu_sched, call_rcu_bh等
    int ncpus;                                              // 处理器数量

    unsigned long gpnum;                            //当前宽限期编号,gpnum > completed,表明正处在宽限期内
    unsigned long completed;                        //上一个结束的宽限期编号,如果与gpnum相等,表明RCU空闲 
    ...
        unsigned long gp_max;                                   //最长的宽限期时间,jiffies        
    ...
}

/*
 * Definition for node within the RCU grace-period-detection hierarchy.
 */
struct rcu_node {
        raw_spinlock_t __private lock;          //保护本节点的自旋锁
        unsigned long gpnum;                    //本节点宽限期编号,等于或小于根节点的gpnum
        unsigned long completed;                //本节点上一个结束的宽限期编号,等于或小于根节点的completed
        unsigned long qsmask;                       //QS状态位图,某位为1,代表对应的成员没有经历QS状态
        unsigned long qsmaskinit;                //正常宽限期开始时,QS状态的初始值
    ...    
    int grplo;      //该分组的CPU最小编号
    int grphi;      //该分组的CPU最大编号
    u8  grpnum;     //该分组在上一层分组里的编号
    u8  level;      //在树中的层级,Root为0
    ...

        struct rcu_node *parent; //指向父节点
}

/* Per-CPU data for read-copy update. */
struct rcu_data {
    unsigned long   completed;      //本CPU看到的已结束的宽限期编号
    unsigned long   gpnum;          //本CPU看到的最高宽限期编号
    union rcu_noqs cpu_no_qs;       //记录本CPU是否经历QS状态
    bool core_need_qs;              //RCU需要本CPU上报QS状态
    unsigned long grpmask;      //本CPU在分组的位图中的掩码
    struct rcu_segcblist;               //回调函数链表,用于存放call_rcu注册的延后执行的回调函数
    ...
}

4 . RCU更新接口

我们看到了RCU的写端调用了synchronize_rcu/call_rcu两种类型的接口,事实上Linux内核提供了三种不同类型的RCU,因此也对应了相应形式的接口。

来张图:

  1. 可抢占RCU:rcu_read_lock/rcu_read_unlock来界定区域,在读端临界区可以被其他进程抢占;

  2. 不可抢占RCU(RCU-sched)rcu_read_lock_sched/rcu_read_unlock_sched来界定区域,在读端临界区不允许其他进程抢占;

  3. 关下半部RCU(RCU-bh)rcu_read_lock_bh/rcu_read_unlock_bh来界定区域,在读端临界区禁止软中断;

  4. 从图中可以看出来,不管是同步还是异步接口,最终都是调到__call_rcu接口,它是接口实现的关键,所以接下来分析下这个函数了;

5 . __call_rcu

函数的调用流程如下:

链表的维护关系如下图所示:

那么通过__call_rcu注册的这些回调函数在哪里调用呢?答案是在RCU_SOFTIRQ软中断中:

6 . 宽限期开始与结束

既然涉及到宽限期GP的操作,都放到了rcu_gp_kthread内核线程中了,那么来看看这个内核线程的逻辑操作吧:

7 . 静止状态检测及报告

很显然,对这种状态的检测通常都是周期性的进行,放置在时钟中断处理中就是情理之中了:

8 . 状态机变换

如果要观察整个状态机的变化,跟踪一下trace_rcu_grace_period接口的记录就能发现:

/*
 * Tracepoint for grace-period events.  Takes a string identifying the
 * RCU flavor, the grace-period number, and a string identifying the
 * grace-period-related event as follows:
 *
 *  "AccReadyCB": CPU acclerates new callbacks to RCU_NEXT_READY_TAIL.
 *  "AccWaitCB": CPU accelerates new callbacks to RCU_WAIT_TAIL.
 *  "newreq": Request a new grace period.
 *  "start": Start a grace period.
 *  "cpustart": CPU first notices a grace-period start.
 *  "cpuqs": CPU passes through a quiescent state.
 *  "cpuonl": CPU comes online.
 *  "cpuofl": CPU goes offline.
 *  "reqwait": GP kthread sleeps waiting for grace-period request.
 *  "reqwaitsig": GP kthread awakened by signal from reqwait state.
 *  "fqswait": GP kthread waiting until time to force quiescent states.
 *  "fqsstart": GP kthread starts forcing quiescent states.
 *  "fqsend": GP kthread done forcing quiescent states.
 *  "fqswaitsig": GP kthread awakened by signal from fqswait state.
 *  "end": End a grace period.
 *  "cpuend": CPU first notices a grace-period end.
 */

大体流程如下:

9 . 总结

参考

Verification of the Tree-Based Hierarchical Read-Copy Update in the Linux Kernel

Documentation/RCU

What is RCU, Fundamentally?

What is RCU? Part 2: Usage

RCU part 3: the RCU API

Introduction to RCU

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8