Барьер памяти
И компилятор, и процессор вправе менять порядок обращений к памяти, если программа в одном потоке от этого не меняется. Но с прерываниями, DMA и регистрами периферии порядок важен. Барьер памяти — граница, через которую такие перестановки запрещены.
Обновлено Редакция GAW
Барьер памяти (memory barrier) — команда или конструкция, через которую нельзя переставлять обращения к памяти: всё, что записано в программе до барьера, должно произойти раньше того, что после. Переставлять обращения могут двое — компилятор при оптимизации и сам процессор, — и барьеры бывают для каждого из них.
Барьеры процессора
Arm в руководстве по Cortex-M3 (разд. 2.2.4) объясняет, почему порядок команд не гарантирует порядок обращений: процессор может переупорядочивать их ради скорости, у него несколько шин, разные области памяти отвечают с разными тактами ожидания, часть обращений буферизуется. Если порядок важен, ставят барьер:
| Команда | Гарантия |
|---|---|
| DMB | обращения к памяти до барьера завершаются раньше обращений после него |
| DSB | следующая команда не начнётся, пока не завершатся все обращения |
| ISB | конвейер очищается, последующие команды выбираются заново |
Заметка Arm AN321 уточняет: Cortex-M3 и M4 и так выполняют обращения в порядке программы, и DMB там избыточен, но его требует архитектура перед запуском DMA и при обмене с другим процессором: Arm рекомендует его ради переносимости на другие ядра Arm, особенно в системах с несколькими ведущими устройствами шины. Барьер только упорядочивает обращения — кэш данных он не очищает.
Барьер компилятора
Компилятор свободно переставляет обычные обращения к памяти, и volatile от этого не спасает: по руководству GCC обычные обращения не упорядочены относительно volatile. Барьер компилятора — вставка asm volatile ("" ::: "memory"): она запрещает переносить обращения через себя и заставляет перечитать из памяти значения, но процессор при этом ничего не делает. Функции CMSIS __DMB(), __disable_irq() и __enable_irq() содержат такой барьер, как и cli()/sei() в avr-libc.
Не путать барьер памяти с критической секцией: барьер задаёт порядок обращений, но не защищает от прерывания посередине. Типичный пример — кольцевой буфер: байт пишется в буфер, потом обновляется индекс, и барьер не даёт поменять их местами. Разбор с кодом — в статье «Прерывания и общие данные».
Частые вопросы
Чем DMB отличается от DSB и ISB?
DMB гарантирует, что обращения к памяти до барьера завершатся раньше обращений после него. DSB строже: следующая команда вообще не выполнится, пока не завершатся все обращения. ISB очищает конвейер, чтобы последующие команды выбирались заново — нужен, например, после изменения настроек, влияющих на выполнение кода.
Нужен ли __DMB() на STM32F1?
По Arm AN321 ядра Cortex-M3 и M4 выполняют обращения к памяти в порядке программы, и DMB на них избыточен. Но Arm рекомендует ставить барьеры там, где их требует архитектура, — перед настройкой DMA, при обмене с другим процессором через семафоры и почтовые ящики, — чтобы код оставался правильным при переносе на другие ядра Arm, особенно в системах с несколькими ведущими устройствами шины (AN321, разд. 3.2).
Делает ли volatile барьер памяти?
Нет. В руководстве GCC сказано, что обычные (не volatile) обращения могут переставляться относительно volatile. Чтобы компилятор не переносил запись в буфер через запись индекса, нужен барьер компилятора — asm volatile с clobber «memory» или функции CMSIS, которые его содержат.
Источники
- Arm. Cortex-M3 Devices Generic User Guide, DUI 0552A, 2010 — разд. 2.2.4: порядок команд не всегда гарантирует порядок обращений к памяти (переупорядочивание, несколько шин, такты ожидания, буферизация); DMB — завершение обращений к памяти до последующих обращений, DSB — до выполнения последующих команд, ISB — сброс конвейера
- Arm. Application Note 321: ARM Cortex-M Programming Guide to Memory Barrier Instructions, DAI 0321A, 2012 — разд. 3.5, 4.18: Cortex-M3/M4 выполняют обращения в порядке программы, DMB там избыточен, но Arm рекомендует барьеры ради переносимости; DMB перед запуском DMA и при обмене с другим процессором; после CPSID барьер не нужен
- GCC 16.2 Manual: When is a Volatile Object Accessed?; Extended Asm (clobber «memory») — не-volatile обращения не упорядочены относительно volatile; volatile — не барьер; clobber «memory» — барьер для компилятора: значения, доступные через память, перечитываются, процессор при этом не останавливается
- Arm. CMSIS 6.4.0: CMSIS/Core/Include/cmsis_gcc.h — __DMB(), __disable_irq(), __enable_irq() — asm volatile с clobber «memory»
Нашли ошибку?