C++ 递减单字节(易失性)数组的元素不是原子的!为什么? (另外:我如何在 Atmel AVR mcus/Arduino 中强制原子性)
C++ decrementing an element of a single-byte (volatile) array is not atomic! WHY? (Also: how do I force atomicity in Atmel AVR mcus/Arduino)
我只是浪费了几天,从字面上看,大约 25 个小时的工作,因为我试图通过一些我不知道的简单事情来调试我的代码。
事实证明,在 AVR ATmega328 8 位微控制器 (Arduino) 上,在 C++ 中递减单字节数组的元素不是原子操作,需要原子访问保护(即关闭中断)。为什么是这样???此外,确保对 Atmel AVR 微控制器上的变量进行原子访问的所有 C 技术有哪些?
这是我所做的简化版本:
//global vars:
const uint8_t NUM_INPUT_PORTS = 3;
volatile uint8_t numElementsInBuf[NUM_INPUT_PORTS];
ISR(PCINT0_vect) //external pin change interrupt service routine on input port 0
{
//do stuff here
for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
numElementsInBuf[i]++;
}
loop()
{
for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
{
//do stuff here
numElementsInBuf[i]--; //<--THIS CAUSES ERRORS!!!!! THE COUNTER GETS CORRUPTED.
}
}
这是适合的循环版本:
loop()
{
for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
{
//do stuff here
noInterrupts(); //globally disable interrupts
numElementsInBuf[i]--; //now it's ok...30 hrs of debugging....
interrupts(); //globally re-enable interrupts
}
}
注意“原子访问保护”,即:在递减之前禁用中断,然后在递减之后重新启用它们。
因为我在这里处理的是单个字节,所以我不知道我需要原子访问保护。为什么在这种情况下我需要它们?这是典型的行为吗?我知道如果这是一个 2 字节值的数组我会需要它们,但为什么是 1 字节值????通常对于 1 字节的值,这里不需要原子访问保护...
更新:阅读此处的“原子访问”部分:http://www.gammon.com.au/interrupts。这是一个很好的来源。
相关(STM32 mcus的答案):
所以我们知道在AVR 8位mcus上读取或写入任何单字节变量是一个原子操作,但是STM32 32位mcus呢? STM32上哪些变量有自动原子读写?答案在这里:.
我不太了解 Arduino 和中断,所以我可能不会在这里回答你的特定问题,但在多线程环境中使用 --
和 ++
递减和递增从来都不是原子的。此外,volatile
在 C++ 中通常也不表示 atomic
(proof)。虽然我知道 volatile
在您对微控制器进行编程时很有意义,但我怀疑我的回答可能不适用于您的情况。
如果将 volatile uint8_t
的数组替换为三个单独的 volatile uint8_t
是否有效?
The ALU supports arithmetic and logic operations between registers or between a constant and a register
它没有提到 ALU 能够直接对内存位置进行操作。所以为了减少一个值,这意味着处理器必须执行几个操作:
- 将值加载到寄存器
- 递减寄存器
- 将值存储回去
因此减量操作不是原子的,除非你做了一些特殊的事情让它成为原子,比如禁用中断。这种 read/modify/write 要求可能比更新内存更常见。
如何使操作成为原子操作的细节取决于平台。较新版本的 C 和 C++ 标准明确支持原子操作;我不知道 ATmega 的工具链是否支持这些更新的标准。
好的,“为什么 incrementing/decrementing 单字节变量不是原子变量?”的答案, and .
在这里回答得很好
既然我得到了 --
递减和 ++
递增操作永远不是原子的答案,即使在字节值上完成(参见上面的答案和 Nick Gammon's link here),我'我想确保后续问题 我如何 是否在 Atmel AVR 微控制器上强制原子性也得到回答,因此这个问题在某种程度上成为一种资源:
以下是我所知道的在 Atmel AVR 微控制器(例如 Arduino)中强制执行原子性的所有技术:
1)方案一(首选方法):
uint8_t SREG_bak = SREG; //save global interrupt state
noInterrupts(); //disable interrupts (for Arduino only; this is an alias of AVR's "cli()")
//atomic variable-access code here
SREG = SREG_bak; //restore interrupt state
2) 选项 2(不太安全,不推荐的方法,因为如果您不小心在内部调用的代码块或库中使用这种方法,它可能会导致您无意中启用嵌套中断一个 ISR):
noInterrupts(); //disable interrupts (Arduino only; is an alias to AVR's "cli()" call)
//atomic variable-access code here
interrupts(); //enable interrupts (Arduino only; is an alias to AVR's "sei()" call)
备选方案 2:
cli(); //clear (disable) interrupts flag; noInterrupts() is simply a macro for this
//atomic variable-access code here
sei(); //set (enable) interrupts flag; interrupts() is simply a macro for this
3) 选项 3(与选项 1 基本相同;只是使用 avr-libc 库中的宏代替,当然在大括号内应用变量范围)
来源:http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
#include <util/atomic.h> //(place at top of code)
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
//atomic access code here
}
相关:
- [我的问答]
- https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/
- ***** [我的回答] Which Arduinos support ATOMIC_BLOCK? [and how can I duplicate this concept in C with
__attribute__((__cleanup__(func_to_call_when_x_exits_scope)))
and in C++ with class constructors and destructors?]
- 关于如何在 STM32 微控制器 中执行此操作,请在此处查看我的回答:What are the various ways to disable and re-enable interrupts in STM32 microcontrollers in order to implement atomic access guards?
我只是浪费了几天,从字面上看,大约 25 个小时的工作,因为我试图通过一些我不知道的简单事情来调试我的代码。
事实证明,在 AVR ATmega328 8 位微控制器 (Arduino) 上,在 C++ 中递减单字节数组的元素不是原子操作,需要原子访问保护(即关闭中断)。为什么是这样???此外,确保对 Atmel AVR 微控制器上的变量进行原子访问的所有 C 技术有哪些?
这是我所做的简化版本:
//global vars:
const uint8_t NUM_INPUT_PORTS = 3;
volatile uint8_t numElementsInBuf[NUM_INPUT_PORTS];
ISR(PCINT0_vect) //external pin change interrupt service routine on input port 0
{
//do stuff here
for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
numElementsInBuf[i]++;
}
loop()
{
for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
{
//do stuff here
numElementsInBuf[i]--; //<--THIS CAUSES ERRORS!!!!! THE COUNTER GETS CORRUPTED.
}
}
这是适合的循环版本:
loop()
{
for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
{
//do stuff here
noInterrupts(); //globally disable interrupts
numElementsInBuf[i]--; //now it's ok...30 hrs of debugging....
interrupts(); //globally re-enable interrupts
}
}
注意“原子访问保护”,即:在递减之前禁用中断,然后在递减之后重新启用它们。
因为我在这里处理的是单个字节,所以我不知道我需要原子访问保护。为什么在这种情况下我需要它们?这是典型的行为吗?我知道如果这是一个 2 字节值的数组我会需要它们,但为什么是 1 字节值????通常对于 1 字节的值,这里不需要原子访问保护...
更新:阅读此处的“原子访问”部分:http://www.gammon.com.au/interrupts。这是一个很好的来源。
相关(STM32 mcus的答案):
所以我们知道在AVR 8位mcus上读取或写入任何单字节变量是一个原子操作,但是STM32 32位mcus呢? STM32上哪些变量有自动原子读写?答案在这里:
我不太了解 Arduino 和中断,所以我可能不会在这里回答你的特定问题,但在多线程环境中使用 --
和 ++
递减和递增从来都不是原子的。此外,volatile
在 C++ 中通常也不表示 atomic
(proof)。虽然我知道 volatile
在您对微控制器进行编程时很有意义,但我怀疑我的回答可能不适用于您的情况。
如果将 volatile uint8_t
的数组替换为三个单独的 volatile uint8_t
是否有效?
The ALU supports arithmetic and logic operations between registers or between a constant and a register
它没有提到 ALU 能够直接对内存位置进行操作。所以为了减少一个值,这意味着处理器必须执行几个操作:
- 将值加载到寄存器
- 递减寄存器
- 将值存储回去
因此减量操作不是原子的,除非你做了一些特殊的事情让它成为原子,比如禁用中断。这种 read/modify/write 要求可能比更新内存更常见。
如何使操作成为原子操作的细节取决于平台。较新版本的 C 和 C++ 标准明确支持原子操作;我不知道 ATmega 的工具链是否支持这些更新的标准。
好的,“为什么 incrementing/decrementing 单字节变量不是原子变量?”的答案
既然我得到了 --
递减和 ++
递增操作永远不是原子的答案,即使在字节值上完成(参见上面的答案和 Nick Gammon's link here),我'我想确保后续问题 我如何 是否在 Atmel AVR 微控制器上强制原子性也得到回答,因此这个问题在某种程度上成为一种资源:
以下是我所知道的在 Atmel AVR 微控制器(例如 Arduino)中强制执行原子性的所有技术:
1)方案一(首选方法):
uint8_t SREG_bak = SREG; //save global interrupt state
noInterrupts(); //disable interrupts (for Arduino only; this is an alias of AVR's "cli()")
//atomic variable-access code here
SREG = SREG_bak; //restore interrupt state
2) 选项 2(不太安全,不推荐的方法,因为如果您不小心在内部调用的代码块或库中使用这种方法,它可能会导致您无意中启用嵌套中断一个 ISR):
noInterrupts(); //disable interrupts (Arduino only; is an alias to AVR's "cli()" call)
//atomic variable-access code here
interrupts(); //enable interrupts (Arduino only; is an alias to AVR's "sei()" call)
备选方案 2:
cli(); //clear (disable) interrupts flag; noInterrupts() is simply a macro for this
//atomic variable-access code here
sei(); //set (enable) interrupts flag; interrupts() is simply a macro for this
3) 选项 3(与选项 1 基本相同;只是使用 avr-libc 库中的宏代替,当然在大括号内应用变量范围)
来源:http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
#include <util/atomic.h> //(place at top of code)
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
//atomic access code here
}
相关:
- [我的问答]
- https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/
- ***** [我的回答] Which Arduinos support ATOMIC_BLOCK? [and how can I duplicate this concept in C with
__attribute__((__cleanup__(func_to_call_when_x_exits_scope)))
and in C++ with class constructors and destructors?] - 关于如何在 STM32 微控制器 中执行此操作,请在此处查看我的回答:What are the various ways to disable and re-enable interrupts in STM32 microcontrollers in order to implement atomic access guards?