RCU-1——内核文档翻译——Tree-RCU-Memory-Ordering.rst

翻译:kernel-5.10\Documentation\RCU\Design\Memory-Ordering\Tree-RCU-Memory-Ordering.rst

====================================================
TREE_RCU 的宽限期内存次序之旅
====================================================

2017 年 8 月 8 日

本文由 Paul E. McKenney 提供

介绍
============

本文档粗略地概述了如何提供 Tree RCU 的宽限期内存次序保证。

Tree RCU 的 Grace Period 内存次序保证是什么?
========================================================

RCU 宽限期为非空闲非离线代码提供极强的内存排序保证。 在给定的 RCU 宽限期结束之后发生的任何代码都可以保证看到在该宽限期开始之前 RCU 读端临界区内的所有访问的影响。 类似地,
在给定 RCU 宽限期开始之前发生的任何代码都可以保证看到在该宽限期结束后 RCU 读取端临界区内的所有访问的影响。

请注意,RCU 调度的读取端临界区包括禁用抢占的任何代码区域。 考虑到每个单独的机器指令都可以被认为是一个非常小的禁止抢占代码区域,我们可以将 ``synchronize_rcu()`` 视为类固
醇的 ``smp_mb()``。

RCU 更新程序通过将更新分为两个阶段来使用此保证,其中一个阶段在宽限期之前执行,另一个在宽限期之后执行。 在最常见的用例中,第一阶段从链接的受 RCU 保护的数据结构中删除一个元
素,第二阶段释放该元素。 为此,任何在第一阶段更新(在常见情况下,删除)之前见证过状态的读者不得在第二阶段更新(在常见情况下,释放)之后见证状态。

RCU 实现使用基于锁的关键部分、内存屏障和每个 CPU 处理的网络提供此保证,如以下部分所述。


树 RCU 宽限期内存排序构建块
===================================================

RCU 宽限期内存排序的主力是 ``rcu_node`` 结构的 ``->lock`` 的关键部分。 这些关键部分使用辅助函数来获取锁,包括``raw_spin_lock_rcu_node()``、``raw_spin_lock_irq_rcu_node()``
和``raw_spin_lock_irqsave_rcu_node()``。 它们的锁释放对应物分别是“raw_spin_unlock_rcu_node()”、“raw_spin_unlock_irq_rcu_node()”和“raw_spin_unlock_irqrestore_rcu_node()”。
为了完整起见,还提供了``raw_spin_trylock_rcu_node()``。 关键是,锁获取函数,包括``raw_spin_trylock_rcu_node()``,都在成功获取锁后立即调用``smp_mb__after_unlock_lock()``。

因此,对于任何给定的 ``rcu_node`` 结构,在上述锁定释放功能之一之前发生的任何访问都将被所有 CPU 视为在上述锁定获取功能之一之后发生的任何访问之前发生。 此外,在任何给定 CPU
上发生在上述锁定释放函数之一之前发生的任何访问将被所有 CPU 视为发生在在同一 CPU 上执行的上述锁定获取函数之一之后发生的任何访问之前,即使 锁释放和锁获取功能在不同的 ``rcu_node``
结构上运行。 Tree RCU 使用这两个排序保证在宽限期内以任何方式涉及的所有 CPU 之间形成一个排序网络,包括在相关宽限期内上线或下线的任何 CPU。

以下石蕊测试展示了这些锁定获取和锁定释放功能的排序效果:

1 int x, y, z;
2
3 void task0(void)
4 {
5 raw_spin_lock_rcu_node(rnp);
6 WRITE_ONCE(x, 1);
7 r1 = READ_ONCE(y);
8 raw_spin_unlock_rcu_node(rnp);
9 }
10
11 void task1(void)
12 {
13 raw_spin_lock_rcu_node(rnp);
14 WRITE_ONCE(y, 1);
15 r2 = READ_ONCE(z);
16 raw_spin_unlock_rcu_node(rnp);
17 }
18
19 void task2(void)
20 {
21 WRITE_ONCE(z, 1);
22 smp_mb();
23 r3 = READ_ONCE(x);
24 }
25
26 WARN_ON(r1 == 0 && r2 == 0 && r3 == 0);

在所有更改都传播到整个系统之后,``WARN_ON()`` 在“时间结束”时进行评估。 如果没有获取函数提供的 ``smp_mb__after_unlock_lock()``,这个 ``WARN_ON()`` 可能会触发,例如在 PowerPC
上。``smp_mb__after_unlock_lock()`` 调用可防止触发此 ``WARN_ON()``。

这种方法必须扩展到包括空闲 CPU,这需要 RCU 的宽限期内存排序保证扩展到当前空闲逗留之前和之后的任何 RCU 读端临界区。 这种情况是通过调用在空闲进入时在 rcu_dynticks_eqs_enter()
和 rcu_dynticks_eqs_exit() 空闲退出时间。 宽限期 kthread 调用 ``rcu_dynticks_snap()`` 和 ``rcu_dynticks_in_eqs_since()``(两者都调用 ``atomic_add_return()`` 为零)来检测空
闲 CPU。


| **小测验**: |
+------------------------------------------------ ----------------------+
| 但是在整个宽限期内都保持离线状态的 CPU 呢? |
+------------------------------------------------ ----------------------+
| **回答**: |
+------------------------------------------------ ----------------------+
| 此类 CPU 将在宽限期开始时脱机,因此宽限期不会期望它们处于静止状态。 宽限期启动和 CPU 热插拔操作之间的竞争被CPU 的叶子``rcu_node`` 结构的``->lock`` 调解了,如上所述。


必须扩展该方法以处理最后一种情况,即唤醒在 synchronize_rcu() 中阻塞的任务。 此任务可能与尚未意识到宽限期已结束的 CPU 关联,因此可能尚未服从宽限期的内存排序。 因此,在
``synchronize_rcu()`` 代码路径中``wait_for_completion()`` 返回后有一个``smp_mb()``。


| **小测验**: |
+------------------------------------------------ ----------------------+
| 什么? 在哪里??? 从 `wait_for_completion()`` 返回后,我没有看到任何 ``smp_mb()``!!! |
+------------------------------------------------ ----------------------+
| **回答**: |
+------------------------------------------------ ----------------------+
| 那是因为我在创建本文档的过程中发现了对 ``smp_mb()`` 的需求,因此它不太可能在 v4.14 之前进入主线。 感谢 Lance Roy、Will Deacon、Peter Zijlstra 和 Jonathan Cameron 提出的问
题使我对证明需要这种记忆障碍的相当复杂的事件序列敏感。


树 RCU 的宽限期内存排序保证在很大程度上依赖于 ``rcu_node`` 结构的 ``->lock`` 字段,以至于有必要在下一节的图表中缩写此模式。 例如,考虑下面显示的 ``rcu_prepare_for_idle()``
函数,它是针对未来宽限期强制对新到达的 RCU 回调进行排序的几个函数之一:

::
1 static void rcu_prepare_for_idle(void)
2 {
3 bool needwake;
4 struct rcu_data *rdp;
5 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);
6 struct rcu_node *rnp;
7 struct rcu_state *rsp;
8 int tne;
9
10 if (IS_ENABLED(CONFIG_RCU_NOCB_CPU_ALL) ||
11 rcu_is_nocb_cpu(smp_processor_id()))
12 return;
13 tne = READ_ONCE(tick_nohz_active);
14 if (tne != rdtp->tick_nohz_enabled_snap) {
15 if (rcu_cpu_has_callbacks(NULL))
16 invoke_rcu_core();
17 rdtp->tick_nohz_enabled_snap = tne;
18 return;
19 }
20 if (!tne)
21 return;
22 if (rdtp->all_lazy &&
23 rdtp->nonlazy_posted != rdtp->nonlazy_posted_snap) {
24 rdtp->all_lazy = false;
25 rdtp->nonlazy_posted_snap = rdtp->nonlazy_posted;
26 invoke_rcu_core();
27 return;
28 }
29 if (rdtp->last_accelerate == jiffies)
30 return;
31 rdtp->last_accelerate = jiffies;
32 for_each_rcu_flavor(rsp) {
33 rdp = this_cpu_ptr(rsp->rda);
34 if (rcu_segcblist_pend_cbs(&rdp->cblist))
35 continue;
36 rnp = rdp->mynode;
37 raw_spin_lock_rcu_node(rnp);
38 needwake = rcu_accelerate_cbs(rsp, rnp, rdp);
39 raw_spin_unlock_rcu_node(rnp);
40 if (needwake)
41 rcu_gp_kthread_wake(rsp);
42 }
43 }

但是 ``rcu_prepare_for_idle()`` 中唯一对本次讨论真正重要的部分是第 37-39 行。 因此,我们将此函数缩写如下:

.. 内核图:: rcu_node-lock.svg

该框表示 ``rcu_node`` 结构的 ``->lock`` 临界区,顶部的双线表示附加的 ``smp_mb__after_unlock_lock()``。


树 RCU 宽限期内存排序组件
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

树 RCU 的宽限期内存排序保证由许多 RCU 组件提供:

#. `回调注册`_
#. `宽限期初始化`_
#. `自我报告的静止状态`_
#. `动态Tick接口`_
#. `CPU 热插拔接口`_
#. `强制静止状态`_
#. `宽限期清理`_
#. `回调调用`_

以下每一节都详细介绍了相应的组件。


回调注册
^^^^^^^^^^^^^^^^^

如果 RCU 的宽限期保证有任何意义,那么在调用 ``call_rcu()`` 之前发生的任何访问也必须在相应的宽限期之前发生。 这部分RCU的grace period guarantee的实现如下图所示:

.. kernel-figure:: TreeRCU-callback-registry.svg

因为 ``call_rcu()`` 通常只作用于 CPU 本地状态,所以它不提供顺序保证,无论是对它自己还是对更新的第一阶段(同样通常是从 RCU 保护的数据结构中删除一个元素). 它
只是将 ``rcu_head`` 结构排入每个 CPU 列表中,在稍后调用 ``rcu_accelerate_cbs()`` 之前,它不能与宽限期相关联,如上图所示。

左侧显示的一组代码路径通过 note_gp_changes() 调用 rcu_accelerate_cbs() ,或者直接从 call_rcu() 调用(如果当前 CPU 被排队的 rcu_head 结构淹没)或更可能来自
``RCU_SOFTIRQ`` 处理程序。 中间的另一个代码路径仅在使用 “CONFIG_RCU_FAST_NO_HZ=y” 构建的内核中采用,它通过“rcu_prepare_for_idle()”调用“rcu_accelerate_cbs()”。 右
侧的最终代码路径仅在使用 ``CONFIG_HOTPLUG_CPU=y`` 构建的内核中采用,它通过 ``rcu_advance_cbs()``、``rcu_migrate_callbacks``、``rcutree_migrate_callbacks()``
和 ``takedown_cpu()``,在即将离任的 CPU 完全脱机后,它又会在幸存的 CPU 上调用。

在宽限期处理中还有一些其他代码路径会机会性地调用 ``rcu_accelerate_cbs()``。 然而,无论哪种方式,所有 CPU 最近排队的 ``rcu_head`` 结构都与 CPU 领先的 ``rcu_node``
结构的``->lock`` 保护下的未来宽限期编号相关联。 在所有情况下,针对同一 ``rcu_node`` 结构的 ``->lock`` 的任何先前临界区都有完整排序,并且针对任何 ``rcu_node` 的
任何当前任务或 CPU 的先前临界区也有完整排序结构的``->lock``。

下一节将展示此顺序如何确保在 ``call_rcu()`` 之前的任何访问(特别是包括更新的第一阶段)发生在相应的宽限期开始之前。


| **小测验**: |
+------------------------------------------------ ----------------------+
| 但是 ``synchronize_rcu()`` 呢?
+------------------------------------------------ ----------------------+
| **回答**: |
+------------------------------------------------ ----------------------+
| ``synchronize_rcu()`` 将 ``call_rcu()`` 传递给 ``wait_rcu_gp()``,后者调用它。 因此,无论哪种方式,它最终都会归结为“call_rcu()”。


宽限期初始化
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

宽限期初始化由宽限期内核线程执行,它在 ``rcu_gp_init()`` 函数中多次遍历 ``rcu_node`` 树。 这意味着通过宽限期计算显示完整的排序流程将需要复制这棵树。 如果您对此感到困
惑,请注意 ``rcu_node`` 的状态会随着时间而变化,就像赫拉克利特的河流一样。 然而,为了保持 ``rcu_node`` 河流易于处理,宽限期内核线程的遍历分为多个部分,从本节开始,介
绍宽限期初始化的各个阶段。

第一个次序相关的宽限期初始化动作是推进``rcu_state``结构的``->gp_seq``宽限期数计数器,如下所示:

..内核图:: TreeRCU-gp-init-1.svg

实际增量是使用 ``smp_store_release()`` 执行的,这有助于拒绝误报的 RCU CPU 停顿检测。 请注意,只有根 ``rcu_node`` 结构被触及。

第一次通过 ``rcu_node`` 树根据自上一个宽限期开始以来联机或脱机的 CPU 更新位掩码。 在这种 ``rcu_node`` 结构的在线 CPU 数量没有转换为零或从零开始的常见情况下,此遍将仅扫
描叶``rcu_node`` 结构。 但是,如果给定叶 ``rcu_node`` 结构的在线 CPU 数量已从零转变,则将为第一个传入 CPU 调用 ``rcu_init_new_rnp()``。 类似地,如果给定叶 ``rcu_node``
结构的在线 CPU 数量已转换为零,则将为最后一个传出 CPU 调用 ``rcu_cleanup_dead_rnp()``。 下图显示了如果最左边的 ``rcu_node`` 结构使其第一个 CPU 在线并且如果下一个
``rcu_node`` 结构没有在线 CPU(或者,如果最左边的 ``rcu_node`` 结构使其最后一个 CPU离线,如果下一个 ``rcu_node`` 结构没有在线 CPU)。

..内核图:: TreeRCU-gp-init-2.svg

最后的 rcu_gp_init() 通过广度优先遍历 rcu_node 树,将每个 rcu_node 结构的 ->gp_seq 字段设置为来自 rcu_state 结构的更新后的值,如下图所示。

..内核图:: TreeRCU-gp-init-3.svg

此更改还将导致每个 CPU 下次调用 ``__note_gp_changes()`` 时注意到新的宽限期已经开始,如下一节所述。 但是因为宽限期 kthread 在设置每个叶子 ``rcu_node`` 结构的 ``->gp_seq
之前在根开始宽限期(随着 ``rcu_state`` 结构的 ``->gp_seq`` 字段的推进),每个 CPU 对宽限期开始的观察将发生在宽限期实际开始之后


| **小测验**: |
+------------------------------------------------ ----------------------+
| 但是启动宽限期的 CPU 呢? 为什么它在宽限期开始时没有看到宽限期的开始? |
+------------------------------------------------ ----------------------+
| **回答**: |
+------------------------------------------------ ----------------------+
| 在某种深刻的哲学和过度拟人化的意义上,是的,开始宽限期的 CPU 会立即意识到已经这样做了。 然而,如果我们假设 RCU 不是自我意识的,那么即使是开始宽限期的 CPU 也不会真正意
| 识到这个宽限期的开始,直到它第一次调用 __note_gp_changes() 。 另一方面,这个 CPU 可能会得到早期通知,因为它在最后一次 rcu_gp_init() 传递其叶 rcu_node 结构时调用了
| __note_gp_changes() 。


自我报告的静止状态
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

当所有可能阻止宽限期的实体都报告了静止状态(或如后面部分所述,代表它们报告了静止状态)时,宽限期可以结束。 在线非空闲CPU报告自己的静止状态,如下图所示:

.. 内核图:: TreeRCU-qs.svg

这是最后一个 CPU 报告静止状态,表示宽限期结束。 早期的静止状态只会向上推 ``rcu_node`` 树,直到遇到正在等待其他静止状态的 ``rcu_node`` 结构。 然而,排序仍然被保留,因为一
些后来的静止状态将获得那个 `rcu_node`` 结构的 ``->lock``。

任何数量的事件都可能导致 CPU 调用 ``note_gp_changes``(或者直接调用 ``__note_gp_changes()``),此时 CPU 将注意到新宽限期的开始,同时保持其叶子` `rcu_node`` 锁。 因此,
此图中显示的所有执行都发生在宽限期开始之后。 此外,此 CPU 将认为在调用 ``__note_gp_changes()`` 之前开始的任何 RCU 读端临界区都在宽限期之前开始,因此是宽限期必须
等待的临界区。


| **小测验**: |
+------------------------------------------------ ----------------------+
| 但是一个RCU读端临界区可能在宽限期开始之后就已经开始了(前面``->gp_seq``的推进),那么宽限期为什么要等待这样的临界区呢? |
+------------------------------------------------ ----------------------+
| **回答**: |
+------------------------------------------------ ----------------------+
| 宽限期确实没有必要等待这样一个重要的部分。 但是,允许等待它。 而且等待它也很重要,因为这种懒惰的方法比“大爆炸”一次性全部宽限期启动可能更具可扩展性。


如果 CPU 进行上下文切换,则左侧的 ``rcu_note_context_switch()`` 将记录静止状态。 另一方面,如果 CPU 在用户模式下执行时发生调度程序时钟中断,则右侧的
``rcu_sched_clock_irq()`` 将记录静止状态。 无论哪种方式,都会在每个 CPU 变量中记录通过静止状态的过程。

下次 ``RCU_SOFTIRQ`` 处理程序在此 CPU 上执行时(例如,在下一个调度程序时钟中断之后),``rcu_core()`` 将调用 ``rcu_check_quiescent_state()``,这将注意到记录的静态状态,
并调用``rcu_report_qs_rdp()``。 如果 rcu_report_qs_rdp() 验证静止状态确实适用于当前宽限期,它会调用 rcu_report_rnp() 遍历 rcu_node 树,如图底部所示, 从每个“rcu_node”
结构的“->qsmask”字段中清除位,并在结果为零时向上传播树。

请注意,仅当当前 CPU 正在报告以该 rcu_node 结构为首的子树的最后静止状态时,遍历才会向上传递给定的 rcu_node 结构。 一个关键点是,如果一个 CPU 的遍历在给定的 ``rcu_node``
结构处停止,那么另一个 CPU(或者可能是同一个)将从该点向上进行的后续遍历,并且 ``rcu_node` ` ``->lock`` 保证第一个 CPU 的静止状态发生在第二个 CPU 的其余遍历之前。 反复
应用这一思路表明,所有 CPU 的静止状态都发生在最后一个 CPU 遍历根 ``rcu_node`` 结构之前,“最后一个 CPU”是清除根 ``rcu_node`` 结构的``->qsmask`` 字段中最后一位的那个。


动态Tick接口
^^^^^^^^^^^^^^^^^^^^^^^

出于能效考虑,RCU 禁止干扰空闲 CPU。 因此,CPU 需要在进入或离开空闲状态时通知 RCU,这是通过对每个 CPU 变量进行完全有序的返回值原子操作来实现的。 排序效果如下图:

..内核图:: TreeRCU-dyntick.svg

RCU 宽限期内核线程在持有相应 CPU 的叶“rcu_node”结构的“->lock”时对每个 CPU 空闲变量进行采样。 这意味着在空闲期(上图顶部附近的椭圆)之前的任何 RCU 读端临界区都将在当前
宽限期结束之前发生。 同样,当前宽限期的开始将发生在空闲期之后的任何 RCU 读端临界区之前(上图底部附近的椭圆)。

在 <#Forcing%20Quiescent%20States> 下方描述了将其引入完整的宽限期执行。


CPU 热插拔接口
^^^^^^^^^^^^^^^^^^^^^^

RCU 也禁止干扰离线 CPU,这些 CPU 可能会断电并完全从系统中移除。 因此,作为相应 CPU 热插拔操作的一部分,CPU 需要通知 RCU 它们的来来去去。 排序效果如下图:

.. 内核图:: TreeRCU-hotplug.svg

由于 CPU 热插拔操作的频率远低于空闲转换,因此它们的权重更重,因此获取 CPU 的叶 ``rcu_node`` 结构的 ``->lock`` 并更新该结构的 ``->qsmaskinitnext``。 RCU 宽限期内核线程
对该掩码进行采样,以检测自该宽限期开始以来是否已脱机的 CPU。

在 <#Forcing%20Quiescent%20States> 下方描述了将其引入完整的宽限期执行。


强制静止状态
^^^^^^^^^^^^^^^^^^^^^^^^^

如上所述,空闲和离线 CPU 无法报告它们自己的静止状态,因此宽限期内核线程必须代表它们进行报告。 这个过程称为“forcing quiescent states”,每隔几个jiffies重复一次,其排序效
果如下图:

..内核图:: TreeRCU-gp-fqs.svg

每次通过强制静止状态都保证遍历叶子``rcu_node``结构,如果由于最近空闲和/或离线的 CPU 而没有新的静态状态,则只遍历叶子。 但是,如果有一个新的脱机 CPU(如左图)或一个新的闲置
CPU(如右图),相应的静止状态将被驱动到根。 与自我报告的静止状态一样,一旦到达具有不同于其他 CPU 的静止状态的“rcu_node”结构,向上驱动就会停止。


| **小测验**: |
+------------------------------------------------ ----------------------+
| 最左边的 root 驱动器在到达 root ``rcu_node`` 结构之前就停止了,这意味着仍然有 CPU 从属于当前宽限期正在等待的该结构。 鉴于此,最右边的根驱动器怎么可能结束宽限期?
+------------------------------------------------ ----------------------+
| **回答**: |
+------------------------------------------------ ----------------------+
| 好分析! 在 RCU 没有 bug 的情况下,这实际上是不可能的。 但是这张图已经足够复杂了,所以简单性压倒了准确性。 您可以将其视为诗意的许可,或者您可以将其视为在“缝合在一起的
| 图表<#Putting%20It%20All%20Together>”中解决的误导。


宽限期清理
^^^^^^^^^^^^^^^^^^^^^

宽限期清理首先广度优先扫描 ``rcu_node`` 树,推进所有 ``->gp_seq`` 字段,然后推进 ``rcu_state`` 结构的 ``->gp_seq`` 字段。 排序效果如下图:

..内核图:: TreeRCU-gp-cleanup.svg

 

 

如图底部的椭圆形所示,一旦宽限期清理完成,下一个宽限期就可以开始了。


| **小测验**: |
+------------------------------------------------ ----------------------+
| 但宽限期究竟何时结束? |
+------------------------------------------------ ----------------------+
| **回答**: |
+------------------------------------------------ ----------------------+
| 没有一个有用的单一时间点可以说宽限期在这点结束。 最早的合理候选者是在最后一个 CPU 报告其静止状态后立即,但可能在 RCU 意识到这一点之前几毫秒。 最新的合理候选者是一旦
| ``rcu_state`` 结构的 ``->gp_seq`` 字段被更新,但很可能到那时一些 CPU 已经完成了它们更新的第二阶段。 简而言之,如果你要和 RCU 一起工作,你需要学会接受不确定性。


回调调用
^^^^^^^^^^^^^^^^^^^^

一旦给定 CPU 的叶“rcu_node”结构的“->gp_seq”字段已更新,该 CPU 就可以开始调用其等待此宽限期结束的 RCU 回调。 这些回调由 ``rcu_advance_cbs()`` 标识,通常由 ``__note_gp_changes()``
调用。 如下图所示,此调用可以由调度时钟中断(左侧的``rcu_sched_clock_irq()``)或空闲条目(右侧的``rcu_cleanup_after_idle()``)触发,但仅适用于使用 ``CONFIG_RCU_FAST_NO_HZ=y``
构建的内核)。 无论哪种方式,都会引发 ``RCU_SOFTIRQ``,这会导致 ``rcu_do_batch()`` 调用回调,这反过来又允许这些回调执行(直接或间接通过唤醒)每个更新所需的第二阶段处理。

.. kernel-figure:: TreeRCU-callback-invocation.svg

请注意,回调调用也可以由任意数量的极端情况代码路径提示,例如,当 CPU 注意到它有过多的回调排队时。 在所有情况下,CPU 在调用回调之前获取其叶``rcu_node`` 结构的``->lock``,
这会保留针对新完成的宽限期所需的顺序。

但是,如果回调函数与其他 CPU 通信,例如进行唤醒,则该函数负责维护顺序。 例如,如果回调函数唤醒了一个在其他 CPU 上运行的任务,则回调函数和被唤醒的任务都必须正确排序。 要了
解为什么这很重要,请考虑 `grace-period cleanup <#Grace-Period%20Cleanup>`__ 图的上半部分。 回调可能运行在最左边的叶子``rcu_node``结构对应的CPU上,被唤醒的任务将运行在最右
边的叶子``rcu_node``结构对应的CPU上,以及grace-period内核线程可能还没有到达最右边的叶子。 在这种情况下,宽限期的内存排序可能尚未到达该 CPU,因此回调函数和唤醒的任务必须再
次提供正确的排序。


把它们放在一起
~~~~~~~~~~~~~~~~~~~~~~~

拼接图在这里:

.. 内核图:: TreeRCU-gp.svg

 

 

法律声明
~~~~~~~~~~~~~~~

本作品仅代表作者观点,不代表 IBM 观点。

Linux 是 Linus Torvalds 的注册商标。

其他公司、产品和服务名称可能是其他公司的商标或服务标记。

 

文档网页位置:

https://www.kernel.org/doc/html/v6.0-rc7/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.html

 

posted on 2022-12-24 15:44  Hello-World3  阅读(260)  评论(0编辑  收藏  举报

导航