rcu_read_lock и порядок памяти x86-64 - PullRequest
1 голос
/ 09 апреля 2019

В вытесняемом ядре SMP rcu_read_lock компилирует следующее:

current->rcu_read_lock_nesting++;
barrier();

, где barrier - директива компилятора, которая ничего не компилирует.

Итак, согласно Intel X86-64 порядок упорядочения памяти:

В старых хранилищах можно переупорядочивать нагрузки в разные места

почему реализация на самом деле нормальная?

Рассмотримследующая ситуация:

rcu_read_lock();
read_non_atomic_stuff();
rcu_read_unlock();

Что препятствует «утечке» read_non_atomic_stuff вперед rcu_read_lock, заставляя его работать одновременно с кодом восстановления, запущенным в другом потоке?

1 Ответ

2 голосов
/ 09 апреля 2019

Для наблюдателей на других процессорах это ничто не мешает. Вы правы, переупорядочение 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 - это «классический RCU», который реализует synchronize_rcu() как for_each_possible_cpu(cpu) run_on(cpu);, чтобы заставить reclaimer выполняться на каждом ядре, которое могло выполнить операцию RCU (то есть на каждом ядре). Как только это будет сделано, мы знаем, что где-то там произошел полный барьер памяти как часть переключения.

Так что да, RCU не следует классическому методу, когда вам нужен полный барьер памяти (включая StoreLoad), чтобы заставить ядро ​​ждать, пока первое хранилище не станет видимым, прежде чем выполнять какие-либо операции чтения. RCU позволяет избежать издержек, связанных с полным барьером памяти на пути чтения. Это одна из главных привлекательностей для него, помимо избежания конфликтов.

...