在任何架构上,有些数据类型可以被原子性读取和写入,而其他类型将需要多个时钟周期,并且在操作过程中可能会被中断,如果这些数据被在线程间共享,则可能导致损坏。
在 8 位单核 AVR 微控制器上(例如 Arduino Uno、Nano 或 Mini 使用的 ATmega328 MCU),仅具有 8 位数据类型的原子读取和写入(在 gcc 编译器和 gnu C 或 gnu C++ 语言下)。我经历了超过 25 小时的调试马拉松,然后在不到 2 天内编写了此答案。有关 AVR 8 位微控制器在使用 AVR-libc 库编译的 gcc 编译器下具有自然原子写入和自然原子读取的更多信息和文档,请参见本问题底部。
在(32 位)STM32 单核微控制器上,任何 32 位或更小的数据类型都是确定自动原子性的(在使用 gcc 编译器和 gnu C 或 gnu C++ 语言编译时,在 ISO C 和 C++ 中直到 2011 版本才有 _Atomic 类型和 std::atomic<> 类型的保证)。这包括 bool/_Bool、int8_t/uint8_t、int16_t/uint16_t、int32_t/uint32_t、float 和所有指针。唯一的非原子类型是 int64_t/uint64_t、double(8 字节)和 long double(也是 8 字节)。我在此写了相关内容:
现在我需要知道在我的64位Linux计算机上哪些类型是确定自动原子的?
我的计算机有一个x86-64处理器和Linux Ubuntu操作系统。
我可以使用Linux头文件和gcc扩展程序。
我在gcc源代码中看到了一些有趣的东西,表明至少32位的int
类型是原子的。例如,Gnu++头文件<bits/atomic_word.h>
存储在我的计算机上的/usr/include/x86_64-linux-gnu/c++/8/bits/atomic_word.h
,并且在这里在线,其中包含以下内容:
typedef int _Atomic_word;
所以,int
显然是原子性的。
而 Gnu++ 头文件 <bits/types.h>
,被 <ext/atomicity.h>
包含,在我的计算机上存储在 /usr/include/x86_64-linux-gnu/bits/types.h
中,其包含了以下内容:
/* C99: An integer type that can be accessed as an atomic entity,
even in the presence of asynchronous interrupts.
It is not currently necessary for this to be machine-specific. */
typedef int __sig_atomic_t;
因此,
int
显然是原子性的。以下是一些示例代码,以展示我所说的内容...
当我说我想知道哪些类型具有自然的原子读取和自然的原子写入,但不具有原子增量、减量或复合赋值时,就是这个意思。
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
}
}
C _Atomic
类型和 C++ std::atomic<>
类型
我知道 C11 及之后的版本提供了 _Atomic
类型,例如下面这个:
const _Atomic int32_t i;
// or (same thing)
const atomic_int_least32_t i;
请看这里:
C++11及更高版本提供了像下面这样的std::atomic<>
类型:
const std::atomic<int32_t> i;
// or (same thing)
const atomic_int32_t i;
请见以下内容:
请参见:
C11和C++11中的“原子”类型提供原子读取和原子写入,以及原子递增运算符、递减运算符和复合赋值...
... 但那并不是我要说的。
我想知道哪些类型只具有自然原子读取和写入。对于我所说的内容,递增、递减和复合赋值将会是非自然原子的。
2022年4月14日更新
我与ST公司的一位人员进行了一些交流,似乎STM32微控制器仅在以下情况下保证特定大小变量的原子读写:
- 使用汇编语言。
- 使用C11的
_Atomic
类型或C++11的std::atomic<>
类型。 - 使用带gnu语言和gcc扩展的gcc编译器。
- 我最感兴趣的是这个,因为我过去的10年里一直以此为前提,没有意识到这一点。我希望能找到gcc编译器手册以及其中解释这些原子访问保证的地方。我们应该检查:
- 8位AVR ATmega微控制器的AVR gcc编译器手册。
- 32位ST微控制器的STM32 gcc编译器手册。
- 我的64位Ubuntu计算机上是否存在x86-64 gcc编译器手册?
- 我最感兴趣的是这个,因为我过去的10年里一直以此为前提,没有意识到这一点。我希望能找到gcc编译器手册以及其中解释这些原子访问保证的地方。我们应该检查:
到目前为止我的研究:
AVR gcc: 没有 AVR gcc 编译器手册。相反,在这里使用 AVR-libc 手册:https://www.nongnu.org/avr-libc/ --> "Users Manual" 链接。
- AVR-libc用户手册中的
<util/atomic>
部分 支持了我的观点,即对于 AVR 上编译时使用 gcc 编译的 8 位类型,已经具有 自然的原子读取和 自然的原子写入,它通过下面的语句来表明 8 位读写在已经是原子操作 (强调已添加):
Typical example that requires atomic access is a 16 (or more) bit variable that is shared between the main execution path and an ISR.
- 它正在讨论 C 代码,而不是汇编代码,因为在该页面提供的所有示例都是以 C 语言形式给出的,包括紧随该引用之后的
volatile uint16_t ctr
变量的示例。
- AVR-libc用户手册中的
is_always_lock_free
来检测哪些类型是原子读取/更新的。(并且你必须使用atomic<>
才能获得原子行为。) - Raymond Chenstd::atomic
。因此问题可能是,哪些原子类型是无锁的?但这还不是全部,即使是无锁的原子类型也存在不是单个指令的原子操作。 - Passer Bystd::atomic<>::is_always_lock_free()
函数返回true当且仅当编译器可以保证该std::atomic类型永远不需要隐式锁定/解锁互斥锁来实现其原子性保证。这可能是您想要的。 - Jeremy Friesnerstatic_assert(std::atomic<int32_t>::is_always_lock_free())
来验证编译器是否支持底层CPU操作,然后使用value.load(std::memory_order_relaxed)
来执行无序读取或value.store(newvalue, std::memory_order_relaxed)
执行无序写入。无序读取/写入几乎总是编译为单个加载或存储指令。 - Raymond Chen