nb:对于这个问题,我不是在谈论C ++ 语言标准。相反,我说的是针对特定体系结构的GCC编译器实施,因为语言标准 的唯一保证是在C11或更高版本中使用 _ATOMIC
类型Code> std :: Atomic<> 类型C ++ 11或更高版本。另请参阅我在这个问题的底部的更新。
在任何体系结构上,某些数据类型都可以原子读取,并在原子上写下,而其他数据类型则可以进行多个时钟周期,并且可以在操作的中间中断,从而导致损坏如果这些数据在各个线程之间共享。
在 8位单核AVR微控制器上(例如:Arduino uno,Nano或mini使用的Atmega328 MCU),只有 8位数据类型具有原子阅读和写入(使用GCC编译器和 gnu c或 gnu c ++语言)。我在&LT中进行了25小时的调试马拉松比赛; 2天,然后在这里写下此答案。有关更多信息,另请参阅此问题的底部。与使用AVR-LIBC库的GCC编译器一起编译时,对具有自然原子自然写入和自然原子读取的8位变量的文档。
在(32位)STM32单核微控制器上,任何数据类型 32位或较小 均自动自动原子(当使用GCC编译器和GCC编译器和GNU C或GNU C或GNU C或gnu c ++语言,如 iso c和c ++,直到2011年版本的 _ATOMIC
类型在C11和 std :: Atomic>>
中类型C ++ 11 )。其中包括 bool
/ _bool
, int8_t
/ uint8_t
, int16_t
/ uint16_t
, int32_t
/ uint32_t
, float
和所有pointers 。唯一的不是原子类型是 int64_t
/ uint64_t
, double
(8 bytes)和 long double double Double
(也是8个字节)。我在这里写了这篇文章:
- 哪些变量类型/尺寸在stm32 microcontrollers上是原子型?
- stackoverflow.com/a/71625693/4561887"> reading reading一个由ISR 更新的64位
- 变量在STM32微控制器中禁用和重新启用中断以实现原子访问警卫的方法吗?
现在,我需要知道我的 64位Linux Computer 。哪些类型自动自动原子?
我的计算机具有X86-64处理器和Linux Ubuntu OS。
我可以使用Linux标头和GCC扩展名。
我在GCC源代码中看到了几个有趣的东西,这表明至少 32-1代码>类型是原子。例如:gnu ++标头< bits/atomic_word.h>
,该>存储在/usr/include/usr/include/x86_64-linux-gnu/c ++/c ++/c ++/8/bits/bits/atomic_hword.hword.hword.word.word.word.word 在我的计算机上,并且是在这里在线,包含以下内容:
typedef int _Atomic_word;
so, int
显然是原子质。
和gnu ++ header < bits/types.h>
,由< ext/atomicity.h>
包含-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
显然是原子质。
这是一些示例代码,可以显示我在说什么...
...当我说我想知道哪些类型自然具有原子读物,并且自然是原子质写的,但是不是 atomic增量,减少或复合分配。
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;
请参阅此处:请参阅此处:
- https://en.cppreference.com/w/c/thread
- https://en.cppreference.com/w/c/language/language/Atomic
和C ++ 11及以后提供 std :: atomic<>
类型,例如:
const std::atomic<int32_t> i;
// or (same thing)
const atomic_int32_t i;
请参阅此处:
- https://en.cppreference.com/w/cpp/atomic/atomic/atomic
以及这些C11和C ++ 11“原子”类型提供原子读物和原子写入以及原子增量运算符,减少操作员和复合分配 ...
...但这并不是我在说的。
我想知道哪些类型自然具有原子读物,并且天然原子仅写作。对于我所说的,增量,减少和复合分配将自然是原子。
更新2022年4月14日,
我与ST中的某人进行了一些聊天,看来STM32微控制器仅保证原子读取和写入这些条件下某些尺寸的变量:
- 您使用汇编。
- 您使用C11
_Atomic
类型或C ++ 11 std :: Atomic&lt;&gt;
类型。
- 您将使用GCC编译器与GNU语言和GCC扩展程序一起使用。
- 我对最后一个最感兴趣,因为这是我在这个问题顶部的假设的关键似乎是基于过去十年的基础,而没有我意识到这一点。我想帮助查找海湾合作委员会编译器手册以及其中解释这些原子访问的位置,可以保证显然存在。我们应该检查:
- 8位AVR ATMEGA微控制器的AVR GCC编译器手册。
- STM32 GCC编译器手册32位ST微控制器。
- x86-64 GCC编译器手册?
到目前为止我的研究:
-
avr gcc: no avr GCC编译器手册存在。相反,在此处使用AVR-LIBC手册: - &gt; “用户手册”链接。
- avr-libc 部分 备份我的主张 在AVR上 8位类型时,当由GCC编译时,已经自然而然地具有原子读物和自然是原子质写作,当它暗示8位读取和写入已经是原子的(添加了强调):
需要原子访问的典型示例是 16(或更多)位变量,它在主执行路径和ISR之间共享。
- 它是在谈论C代码,而不是组装,因为它在该页面上提供的所有示例都在C中,其中包括
volatile uint16_t Ctr ctr
变量的一个示例。
NB: For this question, I'm not talking about the C or C++ language standards. Rather, I'm talking about gcc compiler implementations for a particular architecture, as the only guarantees for atomicity by the language standards are to use _Atomic
types in C11 or later or std::atomic<>
types in C++11 or later. See also my updates at the bottom of this question.
On any architecture, some data types can be read atomically, and written atomically, while others will take multiple clock cycles and can be interrupted in the middle of the operation, causing corruption if that data is being shared across threads.
On 8-bit single-core AVR microcontrollers (ex: the ATmega328 mcu, used by the Arduino Uno, Nano, or Mini), only 8-bit data types have atomic reads and writes (with the gcc compiler and gnu C or gnu C++ language). I had a 25-hr debugging marathon in < 2 days and then wrote this answer here. See also the bottom of this question for more info. and documentation on 8-bit variables having naturally atomic writes and naturally atomic reads for AVR 8-bit microcontrollers when compiled with the gcc compiler which uses the AVR-libc library.
On (32-bit) STM32 single-core microcontrollers, any data type 32-bits or smaller is definitively automatically atomic (when compiled with the gcc compiler and the gnu C or gnu C++ language, as ISO C and C++ make no guarantees of this until the 2011 versions with _Atomic
types in C11 and std::atomic<>
types in C++11). That includes bool
/_Bool
, int8_t
/uint8_t
, int16_t
/uint16_t
, int32_t
/uint32_t
, float
, and all pointers. The only not atomic types are int64_t
/uint64_t
, double
(8 bytes), and long double
(also 8 bytes). I wrote about that here:
- Which variable types/sizes are atomic on STM32 microcontrollers?
- Reading a 64 bit variable that is updated by an ISR
- What are the various ways to disable and re-enable interrupts in STM32 microcontrollers in order to implement atomic access guards?
Now I need to know for my 64-bit Linux computer. Which types are definitively automatically atomic?
My computer has an x86-64 processor, and Linux Ubuntu OS.
I am okay using Linux headers and gcc extensions.
I see a couple of interesting things in the gcc source code indicating that at least the 32-bit int
type is atomic. Ex: the Gnu++ header <bits/atomic_word.h>
, which is stored at /usr/include/x86_64-linux-gnu/c++/8/bits/atomic_word.h
on my computer, and is here online, contains this:
typedef int _Atomic_word;
So, int
is clearly atomic.
And the Gnu++ header <bits/types.h>
, included by <ext/atomicity.h>
, and stored at /usr/include/x86_64-linux-gnu/bits/types.h
on my computer, contains this:
/* 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;
So, again, int
is clearly atomic.
Here is some sample code to show what I am talking about...
...when I say that I want to know which types have naturally atomic reads, and naturally atomic writes, but not atomic increment, decrement, or compound assignment.
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
types and C++ std::atomic<>
types
I know C11 and later offers _Atomic
types, such as this:
const _Atomic int32_t i;
// or (same thing)
const atomic_int_least32_t i;
See here:
- https://en.cppreference.com/w/c/thread
- https://en.cppreference.com/w/c/language/atomic
And C++11 and later offers std::atomic<>
types, such as this:
const std::atomic<int32_t> i;
// or (same thing)
const atomic_int32_t i;
See here:
- https://en.cppreference.com/w/cpp/atomic/atomic
And these C11 and C++11 "atomic" types offer atomic reads and atomic writes as well as atomic increment operator, decrement operator, and compound assignment...
...but that's not really what I'm talking about.
I want to know which types have naturally atomic reads and naturally atomic writes only. For what I am talking about, increment, decrement, and compound assignment will not be naturally atomic.
Update 14 Apr. 2022
I had some chats with someone from ST, and it seems the STM32 microcontrollers only guarantee atomic reads and writes for variables of certain sizes under these conditions:
- You use assembly.
- You use the C11
_Atomic
types or the C++11 std::atomic<>
types.
- You use the gcc compiler with gnu language and gcc extensions.
- I'm most interested in this last one, since that's what the crux of my assumptions at the top of this question seem to have been based on for the last 10 years, without me realizing it. I'd like help finding the gcc compiler manual and the places in it where it explains these atomic access guarantees that apparently exist. We should check the:
- AVR gcc compiler manual for 8-bit AVR ATmega microcontrollers.
- STM32 gcc compiler manual for 32-bit ST microcontrollers.
- x86-64 gcc compiler manual??--if such a thing exists, for my 64-bit Ubuntu computer.
My research thus far:
-
AVR gcc: no avr gcc compiler manual exists. Rather, use the AVR-libc manual here: https://www.nongnu.org/avr-libc/ --> "Users Manual" links.
- The AVR-libc user manual in the
<util/atomic>
section backs up my claim that 8-bit types on AVR, when compiled by gcc, already have naturally atomic reads and naturally atomic writes when it implies that 8-bit reads and writes are already atomic by saying (emphasis added):
A typical example that requires atomic access is a 16 (or more) bit variable that is shared between the main execution path and an ISR.
- It is talking about C code, not assembly, as all examples it gives on that page are in C, including the one for the
volatile uint16_t ctr
variable, immediately following that quote.
发布评论
评论(2)
从语言标准的角度来看,答案非常简单:它们都没有“绝对自动”原子。
首先,要区分两种“原子”感是很重要的。
一个关于信号的原子量。这样可以确保,例如,当您在
上进行
,那么当前线程中的信号处理程序调用将看到旧值或新值。通常仅通过在一个指令中进行访问来实现这一点,因为信号只能由硬件中断触发,这只能在指令之间到达。例如,x86x = 5
时,volatile sig_atomic_t添加dword ptr [var],12345
,即使没有lock> lock
prefix,从这个意义上讲也是原子。另一个相对于线程是原子量,因此同时访问对象的另一个线程将看到正确的值。这很难正确。特别是,类型
的普通变量volatile sig_atomic_t
是不是原子相对于线程。您需要_ATOMIC
或std :: atomic
得到。请注意,您的实施方式为其类型选择的内部名称不是任何证据。从
typedef int _atomic_word;
我当然不会推断“int
显然是原子”;我不知道实施者在什么意义上使用“原子”一词,或者它是否准确(例如,可以由旧版代码使用)。如果他们想做出这样的承诺,它将在文档中,而不是在无法解释的typedef
bits header中。可以由应用程序程序员看到。您的硬件可能可以使某些类型的访问“自动原子”访问的事实不会告诉您C/C ++级别的任何内容。例如,在x86上,对自然对齐变量的普通全尺寸载荷和存储是原子。但是,在没有
std :: Atomic
的情况下,编译器没有义务 EMIT 普通的全尺寸负载和商店;它有权变得聪明,并以其他方式访问这些变量。它“知道”这将不是问题,因为并发访问将是一场数据竞赛,当然,程序员永远不会使用数据竞赛编写代码,对吗?作为具体的示例,请考虑以下代码:
一个不错的32位整数变量的负载,然后进行一些算术。有什么更无辜的?但是,查看GCC 11.2
-O2
尝试grodbolt :哦亲爱的。部分负载,不与启动。
幸运的是,X86确实保证了对齐的DWORD中包含的16位负载或存储是原子质,即使在P5 Pentium上或更高版本的donomic也是原子。实际上,适用于8个字节内的任何1、2或4字节加载或存储在x86-64上是原子,因此,即使
x
是<,这也是有效的优化代码> std :: atomic&lt; int&gt; 。但是在这种情况下,海湾合作委员会将错过优化。英特尔和AMD都分别保证了这一点。 Intel用于P5 Pentium,后来包括所有X86-64 CPU。没有单个“ x86”文档列出了原子能保证的常见子集。 a 列表结合了这两个供应商的保证;大概它也是其他供应商(例如via / zhaoxin)的原子。
希望还可以保证在将此X86指令转换为AARCH64机器代码的任何模拟器或二进制翻译器中,但这绝对是值得担心的,是否在主机机器上没有匹配的原子性保证。
这是另一个有趣的例子,这次是在ARM64上。按ARMV8-A架构参考手册的B2.2.1,对齐的64位商店是原子。所以这看起来还不错:
godbolt ):
但是,GCC 11.2 -O2给出( 代码> str s,不是任何方式。读者很可能会读取
0x000000000000000000000000deadBeef
。为什么这样做?在寄存器中实现一个64位常数,以固定的说明大小对ARM64进行了几项说明。但是,两个值的两半都是相等的,那么为什么不实现32位值并将其存储到每个半部分呢?
(如果您进行
无符号长 *p; *p = 0xDeadBeefDeadBeef
,则获得stp w1,w1,[x0]
( godbolt )。它看起来更有前途,因为它是一个指令,但是在基本的armv8-a中,实际上它仍然是两个单独的写作,出于原子的目的而言, 之间。在线程 “ https://stackoverflow.com/a/71875175/634919"> are并发无序与围栏写入共享内存不确定的行为吗?有另一个不错的示例,用于ARM32 Thumb,c源要求
>
>未签名的短
要加载一次,但生成的代码将其加载两次。在存在并发写作的情况下,您可能会获得“不可能”的结果。一个人可以在x86-64上引起同样的激动( godbolt
)代码>而不是溢出
TMP
。在X86上,您只需一份指令即可加载全局,但是溢出到堆栈中至少需要两个。因此,如果x
正在通过线程或信号/中断进行同时修改,则断言(y == z)
之后可能会失败。除非使用
std :: atomic
,否则假设语言实际上保证的东西超出了任何实际保证的东西,这真的是不安全的。现代编译器非常了解语言规则的确切限制,并积极地优化。他们可以并且将打破假设他们会做“自然”的代码,如果这超出了语言承诺的范围,并且他们经常以人们永远不会期望的方式做到这一点。The answer from the point of view of the language standard is very simple: none of them are "definitively automatically" atomic.
First of all, it's important to distinguish between two senses of "atomic".
One is atomic with respect to signals. This ensures, for instance, that when you do
x = 5
on avolatile sig_atomic_t
, then a signal handler invoked in the current thread will see either the old or new value. This is usually accomplished simply by doing the access in one instruction, since signals can only be triggered by hardware interrupts, which can only arrive between instructions. For instance, x86add dword ptr [var], 12345
, even without alock
prefix, is atomic in this sense.The other is atomic with respect to threads, so that another thread accessing the object concurrently will see a correct value. This is more difficult to get right. In particular, ordinary variables of type
volatile sig_atomic_t
are not atomic with respect to threads. You need_Atomic
orstd::atomic
to get that.Note well that the internal names your implementation chooses for its types are not evidence of anything. From
typedef int _Atomic_word;
I would certainly not infer that "int
is clearly atomic"; I don't know in what sense the implementers were using the word "atomic", or whether it's accurate (could be used by legacy code, for instance). If they wanted to make such a promise it would be in the documentation, not in an unexplainedtypedef
in abits
header that is never meant to be seen by the application programmer.The fact that your hardware may make certain types of access "automatically atomic" does not tell you anything at the level of C/C++. For instance, it is true on x86 that ordinary full-size loads and stores to naturally aligned variables are atomic. But in the absence of
std::atomic
, the compiler is under no obligation to emit ordinary full-size loads and stores; it is entitled to be clever and access those variables in other ways. It "knows" this will be no problem, because concurrent access would be a data race, and of course the programmer would never write code with a data race, would they?As a concrete example, consider the following code:
A load of a nice 32-bit integer variable, followed by some arithmetic. What could be more innocent? Yet check out the assembly emitted by GCC 11.2
-O2
try on godbolt:Oh dear. A partial load, and unaligned to boot.
Fortunately, x86 does guarantee that a 16-bit load or store contained within an aligned dword is atomic, even if unaligned, on P5 Pentium or later. In fact, any 1, 2, or 4-byte load or store that fits within an aligned 8-byte is atomic on x86-64, so this would be a valid optimization even if
x
had beenstd::atomic<int>
. But in that case GCC would have missed the optimization.Both Intel and AMD separately guarantee this. Intel for P5 Pentium and later which includes all their x86-64 CPUs. There is no single "x86" document that lists the common subset of atomicity guarantees. A stack overflow answer lists combines the guarantees from those two vendors; presumably it's also atomic on other vendors like Via / Zhaoxin.
Hopefully also guaranteed in any emulators or binary-translators that turn this x86 instruction into AArch64 machine code for example, but that's definitely something to worry about if there isn't a matching atomicity guarantee on the host machine.
Here is another fun example, this time on ARM64. Aligned 64-bit stores are atomic, per B2.2.1 of the ARMv8-A Architecture Reference Manual. So this looks fine:
But, GCC 11.2 -O2 gives (godbolt):
That's two 32-bit
str
s, not atomic in any way. A reader may very well read0x00000000deadbeef
.Why do it this way? Materializing a 64-bit constant in a register takes several instructions on ARM64, with its fixed instruction size. But both halves of the value are equal, so why not materialize the 32-bit value and store it to each half?
(If you do
unsigned long *p; *p = 0xdeadbeefdeadbeef
then you getstp w1, w1, [x0]
(godbolt). Which looks more promising as it is a single instruction, but in base ARMv8-A it is in fact is still two separate writes for purposes of atomicity between threads. The LSE2 feature, optional in ARMv8.2-A and mandatory in ARMv8.4-A, does makeldp/stp
atomic under reasonable alignment conditions.)User supercat's answer to Are concurrent unordered writes with fencing to shared memory undefined behavior? has another nice example for ARM32 Thumb, where the C source asks for an
unsigned short
to be loaded once, but the generated code loads it twice. In the presence of concurrent writes, you could get an "impossible" result.One can provoke the same on x86-64 (godbolt):
GCC will reload
x
instead of spillingtmp
. On x86 you can load a global with just one instruction, but spilling to the stack would need at least two. So ifx
is being concurrently modified, either by threads or by signals/interrupts, thenassert(y == z)
afterwards could fail.It really isn't safe to assume anything beyond what the languages actually guarantees, which is nothing unless you use
std::atomic
. Modern compilers know the exact limits of the language rules very well, and optimize aggressively. They can and will break code that assumes they will do what would be "natural", if that is outside the bounds of what the language promises, and they will very often do it in ways that one would never expect.仅如果您在汇编器中写代码,而不是在C中编写代码。
只有如果您在汇编程序中写代码,而不是在C中编写代码,则只有在ISA保证生成的指令是原子质的情况下,我不记得这是否适用于所有ARM指令。
不,那绝对是错误的。
与AVR和STM32中的类型相同:无。
这一切都归结为不能保证C中的变量访问是原子质,因为它可能会在多个指令中进行。或在某些情况下,在ISA不能保证原子质的指令中。
仅在C(和C ++)中可以将原子视为原子的类型是C11/C ++ 11的
_ATOMIC
预选赛的类型。时期。我的这个答案在ee 这里是重复的。它明确地解决了微控制器案例,种族条件,
挥发性
,危险优化等。它还包含一种简单的方法,可以防止中断中的竞赛条件,该方法适用于所有中断无法中断的MCU。该答案的报价:Only in case you write your code in assembler, not in C.
Only in case you write your code in assembler, not in C. Additionally, only if the ISA guarantees that the generated instruction is atomic, I don't remember if this is true for all ARM instructions.
No, that is definitely wrong.
The same types as in AVR and STM32: none.
This all boils down to that a variable access in C cannot be guaranteed to be atomic because it might get carried out in multiple instructions. Or in some cases in instructions for which the ISA doesn't guarantee atomicity.
The only types that can be regarded as atomic in C (and C++) are those with the
_Atomic
qualifier from C11/C++11. Period.This answer of mine at EE here is a duplicate. It addresses the microcontroller cases explicitly, race conditions, use of
volatile
, dangerous optimizations etc. It also contains a simple way to protect from race conditions in interrupts which is applicable to all MCUs where interrupts cannot be interrupted. A quote from that answer: