哪个变量 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
}
}
在上面的代码中,我可以在不使用互斥锁的情况下对哪些变量执行此操作?我的怀疑如下:
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
// 使用这些临界区原子访问守卫强制进行原子访问。
taskENTER_CRITICAL();
// 在这里读或写(现在保证安全)
taskEXIT_CRITICAL();
相关,但没有回答我的问题:
- Atomic operations in ARM
- ARM: Is writing/reading from int atomic?
- (我自己关于 8 位 AVR [和 Arduino] 微控制器中原子性的问题和答案):
- 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 上),以下是正确的:
bool
/_Bool
是“byte-aligned” (1-byte-aligned)
int8_t
/uint8_t
是“byte-aligned” (1-byte-aligned)
int16_t
/uint16_t
是“halfword-aligned” (2-byte-aligned)
int32_t
/uint32_t
是“word-aligned” (4-byte-aligned)
int64_t
/uint64_t
是“doubleword-aligned” (8-byte-aligned) <-- 不保证原子性
float
是“word-aligned” (4-byte-aligned)
double
是“doubleword-aligned” (8-byte-aligned) <-- 不保证原子性
long double
是“doubleword-aligned” (8-byte-aligned) <-- 不保证原子性
- 所有指针都是“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,适用。
进一步阅读:
- ARM: Is writing/reading from int atomic?
- What is the difference between atomic / volatile / synchronized?
- Can variables inside packed structures be read atomically?
关于 2018 年 10 月 30 日更改的说明:
- 我有这个参考:ARMv7 TRM(技术参考手册)。但是,这在两个方面是错误的:1) 这根本不是 TRM! TRM 是一份简短的(~200 pgs)技术参考手册。然而,这是“体系结构参考手册”,而不是 TRM。这是一份更长更通用的文档,因为架构参考手册的数量级约为 ~1000~2000 pgs。 2) 这是针对 ARMv7-A 和 ARMv7-R 处理器的,但是我需要的有关 STM32 mcu 的手册是针对 ARMv7-M 处理器的。
- 这是 ARM Cortex-M7 处理器技术参考手册的正确 link。在线: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 体系结构参考手册。”因此,这是该手册。在线下载 link: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)
到创建原子访问守卫(通常通过关闭中断当读写不是原子)见:
原子"arithmetic"可以被CPU核心寄存器处理!
可以是任何类型一个或四个字节取决于体系结构和指令集
但修改位于内存中的任何变量 至少需要 3 个系统步骤:RMW = 读取内存到寄存器、修改寄存器和将寄存器写入内存。
因此,只有当您控制 CPU 寄存器的使用时,原子修改才有可能,这确实意味着需要使用纯汇编程序,而不是使用 C 或 Cpp 编译器。
当您使用 C\Cpp 编译器时,它会将全局或全局静态变量放在内存中,因此 C\Cpp 不提供任何原子操作和类型
注意:您可以使用例如 "FPU registers" 进行原子修改(如果您确实需要它),但您必须向编译器和 RTOS 隐藏架构具有 FPU。
以下是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
}
}
在上面的代码中,我可以在不使用互斥锁的情况下对哪些变量执行此操作?我的怀疑如下:
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
// 使用这些临界区原子访问守卫强制进行原子访问。 taskENTER_CRITICAL(); // 在这里读或写(现在保证安全) taskEXIT_CRITICAL();
相关,但没有回答我的问题:
- Atomic operations in ARM
- ARM: Is writing/reading from int atomic?
- (我自己关于 8 位 AVR [和 Arduino] 微控制器中原子性的问题和答案):
- 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 上),以下是正确的:
bool
/_Bool
是“byte-aligned” (1-byte-aligned)int8_t
/uint8_t
是“byte-aligned” (1-byte-aligned)int16_t
/uint16_t
是“halfword-aligned” (2-byte-aligned)int32_t
/uint32_t
是“word-aligned” (4-byte-aligned)int64_t
/uint64_t
是“doubleword-aligned” (8-byte-aligned) <-- 不保证原子性float
是“word-aligned” (4-byte-aligned)double
是“doubleword-aligned” (8-byte-aligned) <-- 不保证原子性long double
是“doubleword-aligned” (8-byte-aligned) <-- 不保证原子性- 所有指针都是“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,适用。
进一步阅读:
- ARM: Is writing/reading from int atomic?
- What is the difference between atomic / volatile / synchronized?
- Can variables inside packed structures be read atomically?
关于 2018 年 10 月 30 日更改的说明:
- 我有这个参考:ARMv7 TRM(技术参考手册)。但是,这在两个方面是错误的:1) 这根本不是 TRM! TRM 是一份简短的(~200 pgs)技术参考手册。然而,这是“体系结构参考手册”,而不是 TRM。这是一份更长更通用的文档,因为架构参考手册的数量级约为 ~1000~2000 pgs。 2) 这是针对 ARMv7-A 和 ARMv7-R 处理器的,但是我需要的有关 STM32 mcu 的手册是针对 ARMv7-M 处理器的。
- 这是 ARM Cortex-M7 处理器技术参考手册的正确 link。在线: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 体系结构参考手册。”因此,这是该手册。在线下载 link: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)
到创建原子访问守卫(通常通过关闭中断当读写不是原子)见:
原子"arithmetic"可以被CPU核心寄存器处理!
可以是任何类型一个或四个字节取决于体系结构和指令集
但修改位于内存中的任何变量 至少需要 3 个系统步骤:RMW = 读取内存到寄存器、修改寄存器和将寄存器写入内存。
因此,只有当您控制 CPU 寄存器的使用时,原子修改才有可能,这确实意味着需要使用纯汇编程序,而不是使用 C 或 Cpp 编译器。
当您使用 C\Cpp 编译器时,它会将全局或全局静态变量放在内存中,因此 C\Cpp 不提供任何原子操作和类型
注意:您可以使用例如 "FPU registers" 进行原子修改(如果您确实需要它),但您必须向编译器和 RTOS 隐藏架构具有 FPU。