在抢占式SMP内核上,rcu_read_lock
编译以下内容:
current->rcu_read_lock_nesting++;
barrier();
barrier
是一个编译器指令,编译为无。
因此,根据英特尔的X86-64内存订购白皮书:
货物可能会与旧商店重新订购到不同的位置
为什么实现实际上没问题?
请考虑以下情况:
rcu_read_lock();
read_non_atomic_stuff();
rcu_read_unlock();
是什么防止read_non_atomic_stuff
向前"泄漏"过去rcu_read_lock
,导致它与在另一个线程中运行的回收代码同时运行?
对于其他 CPU 上的观察者,没有什么能阻止这一点。 你是对的,++
存储部分的 StoreLoad 重新排序可以使它在某些加载后全局可见。
因此,我们可以得出结论,只有在此内核上运行的代码才能观察到current->rcu_read_lock_nesting
,或者通过在此处调度远程触发此内核上的内存屏障,或者使用专用机制让所有内核在处理器间中断 (IPI) 的处理程序中执行屏障。 例如,类似于membarrier()
用户空间系统调用。
如果此核心开始运行另一个任务,则保证该任务按程序顺序查看此任务的操作。 (因为它位于同一内核上,并且内核始终按顺序查看自己的操作。 此外,上下文切换可能涉及完整的内存屏障,因此可以在另一个内核上恢复任务,而不会破坏单线程逻辑。 (这将使任何内核都可以安全地查看rcu_read_lock_nesting
当此任务/线程未在任何地方运行时。
请注意,内核在机器的每个内核启动一个 RCU 任务;例如ps
输出显示我的 4c8t 四核上的[rcuc/0]
、[rcuc/1]
、...、[rcu/7]
。 据推测,它们是这种设计的重要组成部分,可以让读者无需等待,没有任何障碍。
我还没有研究RCU的全部细节,但其中一个"玩具"示例 https://www.kernel.org/doc/Documentation/RCU/whatisRCU.txt 是"经典RCU",它实现synchronize_rcu()
作为for_each_possible_cpu(cpu) run_on(cpu);
,以使回收器在每个可能已经执行RCU操作的内核(即每个内核)上执行。 一旦完成,我们知道作为切换的一部分,一定在某个地方发生了完整的内存屏障。
所以是的,RCU 不遵循经典方法,在这种方法中,您需要一个完整的内存屏障(包括 StoreLoad)来使内核等到第一个存储可见后再进行任何读取。RCU 避免了读取路径中完整内存屏障的开销。这是它的主要吸引力之一,除了避免争论。