Назад к блогу

Когда LDAXR не работает: эксклюзивный доступ и кэшируемость на AArch64

Когда LDAXR не работает: эксклюзивный доступ и кэшируемость на AArch64

На практике спинлок на LDAXR/STXR может падать с Data Abort даже на корректном коде — всё зависит от атрибутов памяти, к которой идёт эксклюзивный доступ. Разбираем, как устроены эксклюзивные мониторы на AArch64, почему атомарность гарантируется только для кэшируемой shareable-памяти и как отключение кэшируемости превращает безобидную инструкцию в аппаратное исключение.

Спинлок на паре LDAXR/STXR выглядит безупречно: загрузили слово блокировки эксклюзивно, проверили, записали эксклюзивно, повторили при неудаче. Но на реальном железе — Raspberry Pi 5 с Cortex-A76 — эта конструкция падает с Data Abort прямо на инструкции эксклюзивной загрузки. Разберём, как устроен механизм эксклюзивного доступа, почему он зависит от атрибутов памяти и при каких условиях перестаёт работать.

Зачем нужны эксклюзивные доступы

Пары LDXR/STXR и LDAXR/STXR реализуют атомарное чтение-изменение-запись. Работают они в связке: STXR завершается успешно только если с момента выполнения LDXR/LDAXR по тому же адресу не наблюдалось других записей. Именно этот условный успех и даёт атомарность — без него нельзя построить ни спинлок, ни CAS.

Атомарность обеспечивают эксклюзивные мониторы. У каждого ядра есть собственный локальный монитор, а все ядра имеют доступ к глобальному. Каждый монитор находится в одном из двух состояний — open или exclusive. При выполнении load exclusive монитор переводится в exclusive и запоминает адрес загрузки. При выполнении store exclusive мониторы проверяются: успех возможен, только если все они в состоянии exclusive и адрес совпадает.

Спинлок на этой основе строится как цикл: эксклюзивная загрузка слова, проверка занятости, эксклюзивная запись единицы и повтор при неудаче. Разблокировка выполняется обычной store-release инструкцией, записывающей ноль.

ldaxr %w0, [%2]
cbnz %w0, 1b
mov %w0, #1
stxr %w1, %w0, [%2]
cbnz %w1, 1b

Слово блокировки 32-битное, отсюда w-регистры: 0 означает разблокировано, 1 — заблокировано. Буква «a» в ldaxr — acquire-семантика: она гарантирует, что все обращения к памяти после загрузки не переупорядочиваются перед ней. Разблокировка — stlr wzr, [%0], запись нуля.

Где живёт монитор: shareability

Выбор монитора зависит от shareability. Она бывает Non-Shareable, Inner-Shareable или Outer-Shareable.

Эксклюзивные обращения к памяти, помеченной как Non-shareable, затрагивают только локальный монитор. Обращения к shareable-памяти (Inner или Outer) задействуют и локальный, и глобальный мониторы. Локальный и глобальный мониторы могут разделять логику: один из подходов — реализовать глобальный монитор для кэшируемых мест комбинацией локальных мониторов ядер и логики когерентности кэшей.

В мониторах хранится не полный адрес, а только старшие биты — ERG. На типичной системе ERG совпадает с размером строки кэша.

Требование Arm к памяти

Архитектурно атомарность эксклюзивных инструкций гарантирована для Normal memory со следующими атрибутами:

Inner Shareable, Inner Write-Back, Outer Write-Back Normal memory
with Read allocation hints and Write allocation hints and not transient

Аналогичная гарантия есть для Outer Shareable с теми же Write-Back атрибутами. А вот память типа Device, Non-cacheable или трактуемая как Non-cacheable в реализации с аппаратной когерентностью кэшей может не поддерживать атомарные инструкции.

Это и есть корень проблемы: эксклюзивная операция к shareable-памяти обращается к глобальному монитору, а тот может быть реализован через логику когерентности кэшей. Если кэшируемость отключена, глобальный монитор не может выполнить резервирование — и вместо тихой потери атомарности возникает аппаратно генерируемый Data Abort.

Наблюдаемый сбой

На Raspberry Pi 5 исключение оказалось Data Abort с EC = 37 (0x25) — класс, означающий, что оно возникло в результате инструкции загрузки или сохранения. Исключение пришло с адреса 0x84474. По этому адресу находится ldaxr w0, [x1] — именно на ней и остановился CPU.

Инструкция лежит внутри конструктора SpinLockGuard, непосредственно перед ней стоит str x1, [x0], сохраняющая указатель на блокировку. В дампе регистров на момент исключения x0 содержал 0x2bcf88, x30 — 0x85554.

Замена эксклюзивных операций на обычные ldar и str устранила исключение. Это прямо указывает на то, что блокируются именно эксклюзивные операции, а не доступ к памяти как таковой.

Почему адрес блокировки оказался некэшируемым

Причина — в конфигурации MMU. Глобальный бит кэшируемости данных SCTLR.C на всех ядрах был равен своему значению по умолчанию 0, что отключает кэш данных. При выключенном кэше все загрузки и сохранения в кэшируемую память трактуются как некэшируемые и некогерентные с кэшами этого и других ядер.

Но одного глобального бита мало. Даже при SCTLR.C = 1 атрибут страницы должен быть Write-Back. Cortex-A76 считает страницу кэшируемой только если и Inner, и Outer атрибуты — Write-Back; во всех остальных случаях страница понижается до некэшируемой Normal memory.

Эксперимент с разными конфигурациями MAIR для памяти, на которую ссылается ldaxr, это подтвердил: исключение возникает для Write-Through Transient, Non-cacheable и Write-Through Non-transient, но не для Write-Back Transient и Write-Back Non-transient. Write-Back варианты (0b01110111, 0b11111111) исключения не дают.

Итог: load exclusive на shareable-памяти не работает, пока кэшируемость не включена одновременно глобально (SCTLR.C = 1) и локально (атрибут Write-Back в записи таблицы трансляции).

Пограничные случаи

Что произойдёт, если два ядра одновременно выполнят LDAXR по одному адресу? Оба переведут свои мониторы в exclusive и запомнят адрес. Но при STXR проверяется, что все мониторы в состоянии exclusive и адрес совпадает. После успешного STXR мониторы перестают быть в exclusive, и второй STXR проверку не проходит. Поэтому ровно один из двух завершится успешно.

Отдельно стоит помнить про ERG: два адреса внутри одной гранулы выглядят для монитора как одна запись. На типичной системе гранула совпадает со строкой кэша, так что ложные срабатывания возможны для соседних данных.

Что касается событий, сбрасывающих монитор — прерываний, контекстных свитчей, чужих записей, вытеснения строки кэша — в разобранном материале они не перечислены. Описаны только состояния монитора и условия успеха STXR.

Компромиссы базового спинлока

Цикл ожидания в спинлоке устроен так: загрузка эксклюзивом читает слово блокировки, и если оно не равно нулю, управление возвращается к началу цикла. Ядро в цикле повторно выполняет загрузку и проверку, не переходя в низкопотребляющее состояние.

Отсюда две проблемы. Производительность невысока — ядра тратят циклы на busy-waiting. Справедливость не обеспечивается: ядро может быть заморено, если другие ядра всегда успевают захватить блокировку первыми.

В разобранном материале подход к решению проблемы производительности обозначен как будущая работа.

Что из этого следует на практике

Диагностику подобных сбоев стоит вести от архитектурных требований к атрибутам памяти. Порядок такой: вспомнить гарантированно атомарные комбинации атрибутов и случаи, когда атомарные инструкции могут не поддерживаться; проверить фактический тип памяти и её атрибуты; проверить глобальный бит SCTLR.C; затем изолировать вклад локальных атрибутов, меняя MAIR-паттерн для адреса ldaxr и наблюдая, возникает ли исключение.

Ключевой вывод для переносимости: на конкретной реализации и конфигурации Raspberry Pi 5 с Cortex-A76 операция load exclusive по shareable-памяти не работает без кэшируемости — и глобально, и локально. При этом возникает несколько слоёв, делающих картину запутанной: QEMU говорит одно, Arm ARM — другое, реализация Cortex-A76 — третье.

Поймать эту проблему в QEMU не удалось: QEMU ещё не поддерживает плату rpi5, а кэшируемость в QEMU вообще не моделируется — константа для SCTLR_C нигде не используется. Поэтому, хотя в QEMU всё работает, не стоит считать, что всё идеально, пока не протестируешь на реальном оборудовании. При написании низкоуровневых примитивов синхронизации опираться нужно на проверку на реальном железе, а не только на эмуляцию.

Источники

Похожее