rcu_read_lock 和 x86-64 内存排序
rcu_read_lock and x86-64 memory ordering
在抢占式 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
从 "leaking" 向前超过 rcu_read_lock
,导致它 运行 与另一个线程中的回收代码 运行 并发?
对于其他 CPU 上的观察者来说,没有什么可以阻止这一点。没错,StoreLoad 对 ++
的存储部分进行重新排序可以使其在加载一些内容后全局可见。
因此我们可以得出结论,current->rcu_read_lock_nesting
仅在该核心上被代码 运行 观察到, 或者远程触发了该核心上的内存屏障通过在此处进行调度,或使用专用机制让所有内核在处理器间中断 (IPI) 的处理程序中执行屏障。例如类似于 membarrier()
user-space 系统调用。
如果此核心启动 运行 另一个任务,则保证该任务按程序顺序查看此任务的操作。 (因为它在同一个核心上,并且一个核心总是按顺序看到自己的操作。)此外,上下文切换可能涉及完整的内存屏障,因此可以在 另一个 核心上恢复任务而不会中断单线程逻辑。 (当此任务/线程不在任何地方 运行 时,任何核心都可以安全地查看 rcu_read_lock_nesting
。)
请注意,内核为您机器的每个内核启动一个 RCU 任务;例如ps
输出在我的 4c8t 四核上显示 [rcuc/0]
、[rcuc/1]
、...、[rcu/7]
。想必它们是此设计的重要组成部分,让读者可以无障碍地等待。
我还没有研究 RCU 的全部细节,但是 "toy" 中的一个例子
https://www.kernel.org/doc/Documentation/RCU/whatisRCU.txt 是将 synchronize_rcu()
实现为 for_each_possible_cpu(cpu) run_on(cpu);
的 "classic RCU",以使回收器在可能已执行 RCU 操作的每个核心(即每个核心)上执行。完成后,我们知道作为切换的一部分,一定在某处发生了完整的内存屏障。
所以是的,RCU 不遵循经典方法,您需要一个完整的内存屏障(包括 StoreLoad)来使核心在执行任何读取之前等到第一个存储可见。 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
从 "leaking" 向前超过 rcu_read_lock
,导致它 运行 与另一个线程中的回收代码 运行 并发?
对于其他 CPU 上的观察者来说,没有什么可以阻止这一点。没错,StoreLoad 对 ++
的存储部分进行重新排序可以使其在加载一些内容后全局可见。
因此我们可以得出结论,current->rcu_read_lock_nesting
仅在该核心上被代码 运行 观察到, 或者远程触发了该核心上的内存屏障通过在此处进行调度,或使用专用机制让所有内核在处理器间中断 (IPI) 的处理程序中执行屏障。例如类似于 membarrier()
user-space 系统调用。
如果此核心启动 运行 另一个任务,则保证该任务按程序顺序查看此任务的操作。 (因为它在同一个核心上,并且一个核心总是按顺序看到自己的操作。)此外,上下文切换可能涉及完整的内存屏障,因此可以在 另一个 核心上恢复任务而不会中断单线程逻辑。 (当此任务/线程不在任何地方 运行 时,任何核心都可以安全地查看 rcu_read_lock_nesting
。)
请注意,内核为您机器的每个内核启动一个 RCU 任务;例如ps
输出在我的 4c8t 四核上显示 [rcuc/0]
、[rcuc/1]
、...、[rcu/7]
。想必它们是此设计的重要组成部分,让读者可以无障碍地等待。
我还没有研究 RCU 的全部细节,但是 "toy" 中的一个例子
https://www.kernel.org/doc/Documentation/RCU/whatisRCU.txt 是将 synchronize_rcu()
实现为 for_each_possible_cpu(cpu) run_on(cpu);
的 "classic RCU",以使回收器在可能已执行 RCU 操作的每个核心(即每个核心)上执行。完成后,我们知道作为切换的一部分,一定在某处发生了完整的内存屏障。
所以是的,RCU 不遵循经典方法,您需要一个完整的内存屏障(包括 StoreLoad)来使核心在执行任何读取之前等到第一个存储可见。 RCU 避免了读取路径中完整内存屏障的开销。除了避免争用之外,这是它的主要吸引力之一。