Какие типы / размеры переменных являются атомарными на микроконтроллерах 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
    }
}

В приведенном выше коде, для каких переменных я могу сделать это без использования мьютекса? Мое подозрение заключается в следующем:

  1. volatile bool: безопасно - мьютекс не требуется
  2. volatile uint8_t: безопасно - мьютекс не требуется
  3. volatile uint16_t: безопасно - мьютекс не требуется
  4. volatile uint32_t: безопасно - мьютекс не требуется
  5. volatile uint64_t: Небезопасно - вы должны использовать критический раздел или MUTEX!
  6. volatile float: безопасно - мьютекс не требуется
  7. 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();

Связанные, но не отвечая на мой вопрос:

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.

Дальнейшее чтение:

Примечания об изменениях на 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.

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