Я думаю, что в этом случае и volatile, и atomic, скорее всего, будут работать на практике на 32-битной ARM. По крайней мере, в более старой версии инструментов STM32 я видел, что на самом деле атомарность C была реализована с использованием volatile для небольших типов.
Volatile будет работать, потому что компилятор может не оптимизировать любой доступ к переменной, которая появляется в коде.
Однако сгенерированный код должен отличаться для типов, которые нельзя загрузить в одной инструкции. Если вы используете volatile int64_t
, компилятор с радостью загрузит его двумя отдельными инструкциями. Если ISR выполняется между загрузкой двух половин переменной, вы загрузите половину старого значения и половину нового значения.
К сожалению, использование atomic<int64_t>
также может привести к сбою с подпрограммами обслуживания прерываний, если реализация не является свободной от блокировок. Для Cortex-M 64-битные доступы не обязательно не блокируются, поэтому на атомарность не следует полагаться без проверки реализации. В зависимости от реализации система может заблокироваться, если механизм блокировки не является реентерабельным и прерывание происходит, пока блокировка удерживается. Начиная с C++17, это можно запросить, проверив atomic<T>::is_always_lock_free
. Конкретный ответ для конкретной атомарной переменной (это может зависеть от выравнивания) можно получить, проверив flagA.is_lock_free()
начиная с C++11.
Таким образом, более длинные данные должны быть защищены с помощью отдельного механизма (например, путем отключения прерываний вокруг доступа и создания переменной атомарной или изменчивой.
Таким образом, правильный способ - использовать std::atomic
, пока доступ не блокируется. Если вас беспокоит производительность, может оказаться целесообразным выбрать соответствующий порядок памяти и придерживаться значений, которые можно загрузить в одной инструкции.
Не использовать ни один из них было бы неправильно, компилятор проверит флаг только один раз.
Все эти функции ждут флага, но они переводятся по-разному:
#include <atomic>
#include <cstdint>
using FlagT = std::int32_t;
volatile FlagT flag = 0;
void waitV()
{
while (!flag) {}
}
std::atomic<FlagT> flagA;
void waitA()
{
while(!flagA) {}
}
void waitRelaxed()
{
while(!flagA.load(std::memory_order_relaxed)) {}
}
FlagT wrongFlag;
void waitWrong()
{
while(!wrongFlag) {}
}
Используя volatile, вы получаете цикл, который пересматривает флаг так, как вы хотели:
waitV():
ldr r2, .L5
.L2:
ldr r3, [r2]
cmp r3, #0
beq .L2
bx lr
.L5:
.word .LANCHOR0
Atomic с последовательным последовательным доступом по умолчанию создает синхронизированный доступ:
waitA():
push {r4, lr}
.L8:
bl __sync_synchronize
ldr r3, .L11
ldr r4, [r3, #4]
bl __sync_synchronize
cmp r4, #0
beq .L8
pop {r4}
pop {r0}
bx r0
.L11:
.word .LANCHOR0
Если вас не волнует порядок памяти, вы получаете рабочий цикл, как и в случае с volatile:
waitRelaxed():
ldr r2, .L17
.L14:
ldr r3, [r2, #4]
cmp r3, #0
beq .L14
bx lr
.L17:
.word .LANCHOR0
Использование ни volatile, ни atomic не укусит вас с включенной оптимизацией, так как флаг проверяется только один раз:
waitWrong():
ldr r3, .L24
ldr r3, [r3, #8]
cmp r3, #0
bne .L23
.L22: // infinite loop!
b .L22
.L23:
bx lr
.L24:
.word .LANCHOR0
flag:
flagA:
wrongFlag:
person
PaulR
schedule
18.08.2020
volatile
, чтобы сообщить компилятору, что внутри основногоflag
компилятор может изменить его без уведомления.std::atomic
тоже подойдет, но в данном случае это не особо нужно. - person HS2   schedule 18.08.2020volatile
могут быть переупорядочены относительно операций с буфером, для защиты которого использовался этот флаг. . - person supercat   schedule 18.08.2020volatile
как глобальный барьер для переупорядочения компилятора,volatile
будет надежно работать для координации действий с ISR. При использовании clang и gcc семантикаvolatile
слишком слаба, чтобы ее можно было использовать для этой цели без использования встроенных функций затирания памяти. - person supercat   schedule 19.08.2020sig_atomic_t
, который равенthe (possibly volatile-qualified) integer type of an object that can be accessed as an atomic entity, even in the presence of asynchronous interrupts
. - person KamilCuk   schedule 20.08.2020