linux - rcu_read_lock 和 x86-64 内存排序

标签 linux x86 memory-barriers rcu

在可抢占的 SMP 内核上,rcu_read_lock 编译以下内容:

current->rcu_read_lock_nesting++;
barrier();

barrier 是编译器指令,不会编译成任何内容。

因此,根据 Intel 的 X86-64 内存订购白皮书:

Loads may be reordered with older stores to different locations

为什么实现实际上没问题?

考虑以下情况:

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 输出显示 [rcuc/0], [rcuc/1], ..., [rcu/7] 在我的 4c8t 四核上。大概它们是此设计的重要组成部分,让读者可以无障碍地等待。

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

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

关于linux - rcu_read_lock 和 x86-64 内存排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55583472/

相关文章:

assembly - ARM Cortex M 中的 ISB 指令

c++ - 使用 memory_order_relaxed 如何在典型架构上保证原子变量的总修改顺序?

c++ - 弃用 _writeBarrier()

Linux Shell 脚本剪切包含路径的变量的最后一部分

mysql - 解决带有列名的 mysql linux bash 有 "-"& 从文件查询 mysql

assembly - 处理器的地址线需要更多的说明

assembly - 在 16 位汇编中写入图形像素

linux - 手动运行 Shell 脚本发送新文件通知邮件

linux - 在启动器上安装USB key

assembly - 通过汇编打印十六进制数字