Rcu_read_lock и порядок памяти x86-64

На вытесняемом ядре 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, заставляя его работать одновременно с кодом восстановления, работающим в другом потоке?

Хм? barrier() является барьером памяти компилятора, поэтому никакие обращения внутри области rcu не могут пройти функцию блокировки или разблокировки. Более того, как ясно сказано в руководстве, загрузки не могут быть переупорядочены со старыми хранилищами в местоположения такой же.

Hadi Brais 09.04.2019 02:49

@HadiBrais Но разве загрузки внутри критической секции на стороне чтения не будут переупорядочены аппаратно с помощью rcu_read_lock, который является загрузкой и сохранением в счетчике? Если да, то почему это нормально?

Einheri 09.04.2019 02:51

@HadiBrais: read_non_atomic_stuff() обращается к памяти разные, чем current->rcu_read_lock_nesting. Да, возможно изменение порядка StoreLoad, если наблюдатель на другом ядре прочитает его, не приняв особых мер предосторожности. Но эти особые меры предосторожности являются частью пункта RCU.

Peter Cordes 09.04.2019 05:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
156
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

Другие вопросы по теме