1

在抢占式 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,导致它与在另一个线程中运行的回收代码同时运行?

4

1 回答 1

2

对于其他 CPU 上的观察者,没有什么能阻止这一点。你是对的, StoreLoad 的 store 部分的重新排序++可以使它在你的一些加载后全局可见。

因此,我们可以得出结论,current->rcu_read_lock_nesting只有在此内核上运行的代码才能观察到,或者通过在此处调度远程触发了此内核上的内存屏障,或者使用专用机制让所有内核在处理程序中执行屏障处理器间中断(IPI)。例如,类似于membarrier()用户空间系统调用。


如果此内核开始运行另一个任务,则保证该任务按程序顺序查看此任务的操作。(因为它在同一个核心上,并且一个核心总是按顺序看到自己的操作。)此外,上下文切换可能涉及完整的内存屏障,因此可以在另一个核心上恢复任务而不会破坏单线程逻辑。(这将使任何核心在rcu_read_lock_nesting此任务/线程未在任何地方运行时都可以安全地查看。)

请注意,内核会为您机器的每个内核启动一个 RCU 任务;例如,在我的 4c8t 四核上ps输出显示[rcuc/0], [rcuc/1], ...。[rcu/7]据推测,它们是这种设计的重要组成部分,让读者可以毫无障碍地等待。

我没有研究 RCU 的全部细节,但 https://www.kernel.org/doc/Documentation/RCU/whatisRCU.txtsynchronize_rcu()中的“玩具”示例之一是实现 为的“经典 RCU” for_each_possible_cpu(cpu) run_on(cpu);,以获取回收器在可能已经完成 RCU 操作的每个核心(即每个核心)上执行。一旦完成,我们就知道作为切换的一部分,那里一定发生了一个完整的内存屏障。

所以是的,RCU 不遵循经典方法,您需要一个完整的内存屏障(包括 StoreLoad)来使核心等到第一个存储可见后再进行任何读取。 RCU 避免了读取路径中的完整内存屏障的开销。 除了避免争用之外,这是它的主要吸引力之一。

于 2019-04-09T03:52:54.200 回答