zoukankan      html  css  js  c++  java
  • 非抢占式RCU实现(二),解释:为什么 RCU_NEXT_SIZE 宏值是4?

    参考:2.6.34

    一个很奇怪的问题。

    没有查找到为什么 RCU_NEXT_SIZE的值为4的原因(包括Documentation),主要是在rcu_state中定义了一个四级的list,感到很有意思。

    我给出自己的解释。

    还是看下代码吧,容易解释:

    =========================================================

    引入nxtlist与 RCU_NEXT_RCU_DONE_TAIL

    static void rcu_do_batch(struct rcu_state *rsp, struct rcu_data *rdp)                                              
        |---->if(!cpu_has_callbacks_ready_to_invoke(rdp))
        |-----      return  
        |-----看下cpu_has_callbacks_ready_to_invoke的实现
        |-----      |---- return &rdp->nxtlist != 
        |-----      |-----       rdp->nxttail[RCU_DONE_TAIL];
        |-----......
        |----rdp->nxtlist = *rdp->nxttail[RCU_DONE_TAIL];
        |-----......

      从rcu_do_batch的实现可以看出,首先rcu_do_batch在 RCU_SOFTIRQ的软中断是一定会被执行的,只是在入口处判定是否需要实质性地操作链表,如果 &rdp->nxtlist != rdp->nxttail[RCU_DONE_TAIL] 成立则不会继续运行,即该次软中断在rcu_do_batch中是不会执行实质性内容的(但是千万不要认为在该次软中断中,没有操作链表上的函数就没有执行关键任务,rcu_progress_gp_end, rcu_check_quiescent_state都在软中断被执行了)。

    在rcu_data结构体中引入
    nxtlistRCU_NEXT_RCU_DONE_TAIL的目的很清楚:
        nxtlist保存待处理的链表头,而*nxttail[RCU_NEXT_RCU_DONE_TAIL]中还保存着下次本核结束某个grace period时需要操作
    的链表的头,如果两者相等,则进入rcu_do_batch后将立即退出。

    =========================================================

    =========================================================

    引入RCU_NEXT_TAIL

    static __call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *rcu),
                      struct rcu_state *rsp)
       |----......
       |----*rdp->nxttail[RCU_NEXT_TAIL] = head;
       |----rdp->nxttail[RCU_NEXT_TAIL] = &head->next;
       |----......

    作用:缓存!(单链表,存储最后可插入节点的位置)

    =========================================================

    =========================================================

    引入RCU_NEXT_READY_TAILRCU_NEXT_TAIL
    这两个很容易混淆

    static void __rcu_process_callbacks(struct rcu_state *rsp, struct rcu_data *rdp)
        |----......
        |----rcu_check_quiescent_state(rsp, rdp);
             |----......
             |
             |通过了quiescent state,调用rcu_report_qs_rdp
             |----rcu_report_qs_rdp(rdp->cpu, rsp, rdp, rdp->passed_quiesc_completed)
                  |----......
                  |----rdp->nxttail[RCU_NEXT_READY_TAIL] =
                  |----     rdp->nxttail[RCU_NEXT_TAIL]

    通过了rcu_check_quiecent_state()中的检查,知道该核通过了quiescent state,因此设置rdp->nxttail[RCU_NEXT_READY_TAIL] = rdp->nxttail[RCU_NEXT_TAIL],而RCU_NEXT_TAIL是每次添加链表的缓存地址,因此可以判定RCU_NEXT_READY_TAIL是存储该核下次通过grace period时需被调用的链表头的地址(有点不妥,见下文)为什么是这样?虽然该核已经上报通过了quiescent state,但是该次grace period的运行结束是由最后一个通过quiescent state的所决定的,如果仍有其它核自开启该次grace period后没有通过quiescent state,那么只能返回,以后添加的的链表元数自然也就只能在下次grace period中被调用)。

    不禁会问:RCU_NEXT_TAIL有什么用?
        先说明:本核上rcu_data中挂接的链表元素,只有两种情况可被处理
               (1)超时:

                    if(ULONG_CMP_LT(rsp->jiffies_force_qs), jiffies)
                            force_quiescent_state(rsp, 1);

               (2)该核结束了由某个核开启的grace period:

                        rcu_node检测到该核是最后一个上报通过quiescent state的核.

        描述个场景:A核开启了grace period,B核在系统初始化后上挂了好几个链表节点,A、C、D核先后通过了quiescent state,B核最后通过quiescent state,则B核是最终结束该grace period的核,B核最终将在rcu_do_batch()中处理链表。

         问题:在于B核应该执行链表上的RCU_DONE_TAIL至RCU_NEXT_READY_TAIL的所有任务吗?
         回答:不能。

        原因:B核尽管在rcu_report_qs_rdp中执行了rdp->nxttail[RCU_NEXT_READY_TAIL] = rdp->nxttail[RCU_NEXT_TAIL],但是我们显然的看到由于该grace period并由非B核开启,因此我们无法判断在B核添加链表时其它核是否通过了quiescent state,如果其它核已经通过了quiescent state,对于此后在B核上挂接的链表,如果在该grace period被B核终止后即被调用处理显然是错的。

         如何解决:引入RCU_WAIT_TAIL

    B核终止了一个非本身开启的grace period,而后:

    rcu_report_qs_rnp(unsigned long mask, struct rcu_state *rsp,
                struct rcu_node *rnp, unsigned long flags)
        |----......
        |----rcu_report_qs_rsp(rsp, flags);
             |----......
             |----rcu_start_gp(rsp, flags);
             |    |----......
             |    |----rcu_start_gp_per_cpu(rsp, rnp, rdp);
             |    |    |----_rcu_process_gp_end(rsp, rnp, rdp);
             |    |    |    |----......
             |    |    |    |----rdp->nxttail[RCU_DONE_TAIL] = 
             |    |    |    |       rdp->nxttail[RCU_WAIT_TAIL];
             |    |    |    |----rdp->nxttail[RCU_WAIT_TAIL] = 
             |    |    |    |       rdp->nxttail[RCU_NEXT_READY_TAIL];
             |    |    |    |----rdp->nxttail[RCU_NEXT_READY_TAIL] = 
             |    |    |    |       rdp->nxttail[RCU_NEXT_TAIL];
             |    |    |    |----.......
             |    |    |----......
             |    |----......
             |----.......
        |----......

      可以看出在rcu_process_per_cpu中置:rdp->nxttail[RCU_DONE_TAIL] = rdp->nxttail[RCU_WAIT_TAIL],而非rdp->nxttail[RCU_NEXT_READY_TAIL]。

      个人认为,这样做可以防止——某个核终止了并非由该核开启的grace period,而且该核在grace period时间段内在自己的链表中的添加了链表元素——在该次rcu_do_batch中,这些链表元素却被处理了。

    =========================================================

    关注TAIL指针的重点,在于

    (1)主动开启grace period

    (2)检测到有新的grace period,但是本核缺没有被动的开启grace period

     (3)检测grace period是否结束,及处理

    (4)向rcu_node上报本核已发生quiescent state

    在这4点处将推动TAIL指针的变化。

    记住一点:某个核开启了grace period,但并不一定是该核来结束grace period,可能是自己,也可能是其它核。

  • 相关阅读:
    [JSOI2007][BZOJ1031] 字符加密Cipher|后缀数组
    leetcode Flatten Binary Tree to Linked List
    leetcode Pascal's Triangle
    leetcode Triangle
    leetcode Valid Palindrome
    leetcode Word Ladder
    leetcode Longest Consecutive Sequence
    leetcode Sum Root to Leaf Numbers
    leetcode Clone Graph
    leetcode Evaluate Reverse Polish Notation
  • 原文地址:https://www.cnblogs.com/openix/p/3317380.html
Copyright © 2011-2022 走看看