Какие типы / размеры переменных являются атомарными на микроконтроллерах STM32?
Вот типы данных на микроконтроллерах STM32: http://www.keil.com/support/man/docs/armcc/armcc_chr1359125009502.htm.
Эти микроконтроллеры используют 32-битные процессоры с ядром ARM.
Какие типы данных имеют автоматическое атомарное чтение и атомарную запись?
Я почти уверен, что все 32-битные типы данных это делают (поскольку процессор 32-битный), а все 64-битные типы данных - нет (поскольку для чтения или записи 64-битного слова потребуется не менее 2 операций процессора).), но что насчет bool
(1 байт) и uint16_t
/int16_t
(2 байта)?
Контекст: я делю переменные между несколькими потоками (одноядерными, но несколькими потоками или "задачами", как они называются в FreeRTOS) на STM32, и мне нужно знать, нужно ли мне принудительно применять атомарный доступ, отключая прерывания, используя мьютексы и т. д.
ОБНОВИТЬ:
Ссылаясь на этот пример кода:
volatile bool shared_bool;
volatile uint8_t shared u8;
volatile uint16_t shared_u16;
volatile uint32_t shared_u32;
volatile uint64_t shared_u64;
volatile float shared_f; // 32-bits
volatile double shared_d; // 64-bits
// Task (thread) 1
while (1)
{
// Write to the values in this thread.
// What I write to each variable will vary. Since other threads
// are reading these values, I need to ensure my *writes* are atomic, or else
// I must use a mutex to prevent another thread from reading a variable in the middle
// of this thread's writing.
shared_bool = true;
shared_u8 = 129;
shared_u16 = 10108;
shared_u32 = 130890;
shared_f = 1083.108;
shared_d = 382.10830;
}
// Task (thread) 2
while (1)
{
// Read from the values in this thread.
// What thread 1 writes into these values can change at any time, so I need to ensure
// my *reads* are atomic, or else I'll need to use a mutex to prevent the other
// thread from writing to a variable in the midst of reading
// it in this thread.
if (shared_bool == whatever)
{
// do something
}
if (shared_u8 == whatever)
{
// do something
}
if (shared_u16 == whatever)
{
// do something
}
if (shared_u32 == whatever)
{
// do something
}
if (shared_u64 == whatever)
{
// do something
}
if (shared_f == whatever)
{
// do something
}
if (shared_d == whatever)
{
// do something
}
}
В приведенном выше коде, для каких переменных я могу сделать это без использования мьютекса? Мое подозрение заключается в следующем:
volatile bool
: безопасно - мьютекс не требуетсяvolatile uint8_t
: безопасно - мьютекс не требуетсяvolatile uint16_t
: безопасно - мьютекс не требуетсяvolatile uint32_t
: безопасно - мьютекс не требуетсяvolatile uint64_t
: Небезопасно - вы должны использовать критический раздел или MUTEX!volatile float
: безопасно - мьютекс не требуетсяvolatile double
: Небезопасно - вы должны использовать критический раздел или MUTEX!
Пример критического раздела с FreeRTOS:
- https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html
// Force atomic access with these critical section atomic access guards.
taskENTER_CRITICAL();
// do the (now guaranteed to be safe) read or write here
taskEXIT_CRITICAL();
Связанные, но не отвечая на мой вопрос:
- Атомные операции в ARM
- ARM: запись / чтение из int atomic?
- (Мой собственный вопрос и ответ на атомарность в 8-битных микроконтроллерах AVR [и Arduino]): /questions/38224802/c-umenshenie-elementa-odnobajtovogo-volatile-massiva-ne-yavlyaetsya-atomarnyim-zachem-takzhe-kak-zastavit-atomarnost-v-atmel-avr-mcusarduino/38224813#38224813
3 ответа
Для окончательного, окончательного ответа на этот вопрос, прыгните прямо вниз к разделу под названием " Окончательный ответ на мой вопрос ".
ОБНОВЛЕНИЕ 30 октября 2018 года: я случайно сослался на (слегка) неправильные документы (но в которых говорилось то же самое), поэтому я исправил их в своем ответе здесь. См. "Примечания об изменениях 30 октября 2018 года" в нижней части этого ответа для получения подробной информации.
Я определенно не понимаю каждое слово здесь, но Справочное руководство по архитектуре ARM v7-M ( онлайн-источник; прямая загрузка файла PDF) (НЕ Техническое справочное руководство [TRM], поскольку оно не обсуждает атомарность) подтверждает мои предположения:
Итак... я думаю, что мои 7 предположений в нижней части моего вопроса все правильно. [30 октября 2018 года: Да, это правильно. Подробности смотрите ниже.]
ОБНОВЛЕНИЕ 29 октября 2018 года:
Еще один маленький кусочек:
Ричард Барри, основатель FreeRTOS, эксперт и основной разработчик, заявляет о tasks.c
...
/ * Критическая секция не требуется, потому что переменные имеют тип BaseType_t. * /
... при чтении энергозависимой переменной "unsigned long" (4-байтовой) в STM32. Это означает, что он, по крайней мере, на 100% уверен, что 4-байтовые операции чтения и записи являются атомарными на STM32. Он не упоминает чтения меньших байтов, но для 4-байтовых чтений он окончательно уверен. Я должен предположить, что 4-байтовые переменные, являющиеся собственной шириной процессора, а также выровненные по словам, имеют решающее значение для этого факта.
От tasks.c
строки 2173-2178 в FreeRTOS v9.0.0, например:
UBaseType_t uxTaskGetNumberOfTasks( void )
{
/* A critical section is not required because the variables are of type
BaseType_t. */
return uxCurrentNumberOfTasks;
}
Он использует эту точную фразу...
/ * Критическая секция не требуется, потому что переменные имеют тип BaseType_t. * /
... в двух разных местах в этом файле.
Окончательный ответ на мой вопрос:
Кроме того, при более внимательном рассмотрении TRM на p141, как показано на моем скриншоте выше, я хотел бы отметить следующие ключевые предложения:
В ARMv7-M доступ к атомарному процессору в единственном экземпляре:
• все байтовые обращения.
• все обращения к полусловам с выровненными по полусловам местами.
• доступ ко всем словам в выровненные по словам местоположения.
И, по этой ссылке, для "базовых типов данных, реализованных в ARM C и C++" (то есть: в STM32), верно следующее:
bool
/_Bool
выравнивается по байту (выравнивается по 1 байту)int8_t
/uint8_t
выравнивается по байту (выравнивается по 1 байту)int16_t
/uint16_t
"выровнено по половому слову" (выровнено по 2 байта)int32_t
/uint32_t
выравнивается по слову (выравнивается по 4 байта)int64_t
/uint64_t
"выровнен по двойному слову" (выровнен по 8 байтам) <- НЕ ГАРАНТИРОВАННЫЙ АТОМfloat
выравнивается по слову (выравнивается по 4 байта)double
"выровнен по двойному слову" (выровнен по 8 байтам) <- НЕ ГАРАНТИРОВАННЫЙ АТОМlong double
"выровнен по двойному слову" (выровнен по 8 байтам) <- НЕ ГАРАНТИРОВАННЫЙ АТОМ- все указатели "выровнены по слову" (выровнены по 4 байта)
Это означает, что у меня теперь есть и я понимаю доказательства, которые мне нужны, чтобы окончательно заявить, что все выделенные жирным шрифтом строки чуть выше имеют автоматический атомарный доступ для чтения и записи (но НЕ, конечно, увеличения / уменьшения, что является множественными операциями). Это окончательный ответ на мой вопрос. Я думаю, что единственное исключение из этой атомарности может быть в упакованных структурах, и в этом случае эти иначе естественно выровненные типы данных могут не выровняться естественным образом.
Также обратите внимание, что при чтении Технического справочного руководства "атомарность в единственном экземпляре", очевидно, просто означает "атомарность в одноядерном процессоре" или "атомарность в архитектуре с одним ядром". Это в отличие от "многоядерности", которая относится к "системе многопроцессорной обработки" или архитектуре многоядерных процессоров. Википедия утверждает, что "многопроцессорность - это использование двух или более центральных процессоров (ЦП) в одной компьютерной системе" ( https://en.wikipedia.org/wiki/Multiprocessing).
Моя рассматриваемая архитектура, STM32F767ZI (с ядром ARM Cortex-M7), представляет собой одноядерную архитектуру, так что, по-видимому, применима "атомарность одного экземпляра", как я цитировал выше из TRM.
Дальнейшее чтение:
- ARM: запись / чтение из int atomic?
- В чем разница между атомарным / энергозависимым / синхронизированным?
- Могут ли переменные внутри упакованных структур быть прочитаны атомарно?
Примечания об изменениях на 30 октября 2018 года:
- У меня была эта ссылка: ARMv7 TRM (Техническое справочное руководство). Однако, это неправильно в двух отношениях: 1) Это вовсе не TRM! TRM - это краткое (~200 страниц) техническое справочное руководство. Это, однако, "Справочное руководство по архитектуре", а не TRM. Это гораздо более длинный и более общий документ, так как справочные руководства по архитектуре имеют порядок ~1000~2000 страниц. 2) Это для процессоров ARMv7-A и ARMv7-R, но руководство, которое мне нужно для рассматриваемого mcu STM32, относится к процессору ARMv7-M.
- Вот правильная ссылка на Техническое справочное руководство по процессору ARM Cortex-M7. Онлайн: https://developer.arm.com/docs/ddi0489/latest. PDF: https://static.docs.arm.com/ddi0489/d/DDI0489D_cortex_m7_trm.pdf.
- Правильный TRM чуть выше, на стр. 99 (5-36) говорит: "Для получения дополнительной информации об атомарности см. Справочное руководство по архитектуре ARM®v7-M". Итак, вот это руководство. Ссылка для скачивания в Интернете: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/ddi0403/latest/armv7-m-architecture-reference-manual. PDF: https://static.docs.arm.com/ddi0489/d/DDI0489D_cortex_m7_trm.pdf. Обсуждается атомарность на p79-80 (от A3-79 до A3-80).
В зависимости от того, что вы подразумеваете под атомным.
Если это не простая операция загрузки или хранения, как
a += 1;
тогда все типы не атомарны.
Если это просто хранить или загружать данные 32 бита, то 16-битные и 8-битные типы данных являются атомарными. Если значение в регистре должно быть нормализовано, сохраните 8 и 16 бит, и загрузка может быть не атомарной.
Если ваше оборудование поддерживает битовую полосу, тогда, если битовая полоса используется, битовые операции (установка и сброс) в областях памяти, поддерживающих битовую полосу, являются атомарными.
Заметка.
если ваш код не допускает невыровненных операций, 8- и 16-битные операции могут быть не атомарными.
Атомная "арифметика" может обрабатываться регистрами CPU Core!
Это могут быть любые типы, один или четыре байта, в зависимости от архитектуры и набора команд.
НО модификация любой переменной, находящейся в памяти, требует как минимум 3 системных шага: RMW = чтение памяти для регистрации, изменение регистра и запись в память.
Поэтому атомарная модификация возможна только в том случае, если вы управляете использованием регистров ЦП, это означает, что нужно использовать чистый ассемблер и не использовать компилятор C или Cpp.
Когда вы используете компилятор C\Cpp, он помещает в память глобальную или глобальную статическую переменную, поэтому C\Cpp не предоставляет никаких атомарных действий и типов.
Примечание: вы можете использовать, например, "регистры FPU" для атомарной модификации (если вам это действительно нужно), но вы должны скрыть от компилятора и ОСРВ, что архитектура имеет FPU.