哪个变量 types/sizes 在 STM32 微控制器上是原子的?

Which variable types/sizes are atomic on STM32 microcontrollers?

以下是STM32微控制器上的数据类型:http://www.keil.com/support/man/docs/armcc/armcc_chr1359125009502.htm

这些微控制器使用 32 位 ARM 内核处理器。

哪些数据类型具有自动原子读取和原子写入访问权限?

我很确定所有 32 位数据类型都可以(因为处理器是 32 位的),而所有 64 位数据类型都不能(因为读取或写入至少需要 2 个处理器操作一个 64 位字),但是 bool(1 个字节)和 uint16_t/int16_t(2 个字节)呢?

上下文:我在 STM32 上的多个线程(单核,但多线程,或在 FreeRTOS 中称为“任务”)之间共享变量,我需要知道是否需要通过关闭中断、使用互斥等来强制执行原子访问

更新:

参考此示例代码:

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 (true)
{
    // 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 (true)
{
    // 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 的关键部分示例:

相关,但没有回答我的问题:

  1. Atomic operations in ARM
  2. ARM: Is writing/reading from int atomic?
  3. (我自己关于 8 位 AVR [和 Arduino] 微控制器中原子性的问题和答案):
  4. https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/

取决于你所说的原子是什么意思。

如果不是像

那样简单的加载或存储操作
a += 1;

那么所有类型都不是原子的。

如果是简单的存储或加载32位操作,16位和8位数据类型是原子的。如果必须规范化寄存器中的值,则 8 位和 16 位存储和加载可能不是原子的。

如果您的硬件支持位带,那么如果使用位带,则支持位带的内存区域中的位操作(设置和重置)是原子的

备注。

如果您的代码不允许未对齐操作,则 8 位和 16 位操作可能不是原子操作。

对于这个问题的最终明确答案,请直接跳到下面标题为“我的问题的最终答案”的部分。

2018 年 10 月 30 日更新: 我不小心引用了(稍微)错误的文档(但说的是完全相同的内容),所以我在我的回答中修复了它们这里。有关详细信息,请参阅此答案底部的“关于 2018 年 10 月 30 日更改的说明”。

我肯定不明白这里的每一个字,但是 ARM v7-M 体系结构参考手册 (Online source; PDF file direct download)(不是技术参考手册 [TRM] ,因为它不讨论原子性)验证了我的假设:

所以...我认为我在问题底部的 7 个假设都是正确的。 [2018 年 10 月 30 日:是的,没错。详情见下文。]


2018 年 10 月 29 日更新:

再来一个小花絮:

Richard Barry,FreeRTOS 创始人、专家和核心开发人员,在 tasks.c...

中表示

/* A critical section is not required because the variables are of type BaseType_t. */

...在 STM32 上读取“unsigned long”(4 字节)volatile 变量时。 这意味着他至少 100% 确定 4 字节读写在 STM32 上是原子的。他没有提到 smaller-byte 读取,但对于 4 字节读起来他是绝对肯定的。我必须假设 4 字节变量是本机处理器宽度,而且 word-aligned 对实现这一点至关重要。

来自 tasks.c,FreeRTOS v9.0.0 中的第 2173-2178 行,例如:

UBaseType_t uxTaskGetNumberOfTasks( void )
{
    /* A critical section is not required because the variables are of type
    BaseType_t. */
    return uxCurrentNumberOfTasks;
}

他用的是……

/* A critical section is not required because the variables are of type BaseType_t. */

...在此文件中的两个不同位置。

我的问题的最终答案:所有类型 <= 4 字节(下面 9 行列表中的所有 加粗 类型)都是原子的。

此外,如我上面的屏幕截图所示,在仔细检查 p141 上的 TRM 后,我想指出的关键句子是:

In ARMv7-M, the single-copy atomic processor accesses are:
• all byte accesses.
• all halfword accesses to halfword-aligned locations.
• all word accesses to word-aligned locations.

而且,per this link,对于“在 ARM C 和 C++ 中实现的基本数据类型”(即:在 STM32 上),以下是正确的:

  1. bool/_Bool 是“byte-aligned” (1-byte-aligned)
  2. int8_t/uint8_t 是“byte-aligned” (1-byte-aligned)
  3. int16_t/uint16_t 是“halfword-aligned” (2-byte-aligned)
  4. int32_t/uint32_t 是“word-aligned” (4-byte-aligned)
  5. int64_t/uint64_t 是“doubleword-aligned” (8-byte-aligned) <-- 不保证原子性
  6. float 是“word-aligned” (4-byte-aligned)
  7. double 是“doubleword-aligned” (8-byte-aligned) <-- 不保证原子性
  8. long double 是“doubleword-aligned” (8-byte-aligned) <-- 不保证原子性
  9. 所有指针都是“word-aligned”(4-byte-aligned)

这意味着我现在拥有并理解我需要的证据 最终声明上面的所有粗体行都具有自动原子读写访问权限(但不是 increment/decrement 当然是多次操作)。 这是我的问题的最终答案。 我认为这种原子性的唯一例外可能是打包结构,在这种情况下,这些 otherwise-naturally-aligned 数据类型可能没有自然对齐。

另请注意,在阅读技术参考手册时,“single-copy 原子性”显然仅表示“single-core-CPU 原子性”或“single-CPU-core 架构上的原子性”。这与“multi-copy 原子性”相反,它指的是“多重处理系统”或 multi-core-CPU 架构。维基百科指出“多处理是在单个计算机系统中使用两个或多个中央处理器 (CPU)”(https://en.wikipedia.org/wiki/Multiprocessing)。

我的架构 STM32F767ZI(带有 ARM Cortex-M7 内核)是一个 single-core 架构,显然是“single-copy 原子性”,因为我在上面引用了 TRM,适用。

进一步阅读:

关于 2018 年 10 月 30 日更改的说明:

创建原子访问守卫(通常通过关闭中断当读写不是原子)见:

  1. [我的问答]What are the various ways to disable and re-enable interrupts in STM32 microcontrollers in order to implement atomic access guards?
  2. My doAtomicRead() func here which can do atomic reads withOUT turning off interrupts

原子"arithmetic"可以被CPU核心寄存器处理!

可以是任何类型一个或四个字节取决于体系结构和指令集

但修改位于内存中的任何变量 至少需要 3 个系统步骤:RMW = 读取内存到寄存器、修改寄存器和将寄存器写入内存。

因此,只有当您控制 CPU 寄存器的使用时,原子修改才有可能,这确实意味着需要使用纯汇编程序,而不是使用 C 或 Cpp 编译器。

当您使用 C\Cpp 编译器时,它会将全局或全局静态变量放在内存中,因此 C\Cpp 不提供任何原子操作和类型

注意:您可以使用例如 "FPU registers" 进行原子修改(如果您确实需要它),但您必须向编译器和 RTOS 隐藏架构具有 FPU。