原子地覆盖指针
Atomically overwrite a pointer
我发现了与我遇到的问题类似的问题(即 Is changing a pointer considered an atomic action in C?),但是在编写指针时我找不到任何可以给我明确 yes/no 答案的东西.
我的问题就是这样。我有一个带有指针字段的结构指针数组。
例如
struct my_struct {
void *ptr;
};
struct my_struct *my_struct[10];
每个结构的指针字段由线程读取和写入。
例如
uint8_t index;
for(index = 0; index < 10; index++)
{
if(my_struct[index]->ptr != NULL)
{
my_struct[index]->ptr = NULL;
}
}
周期性地发生一个中断,它将读取(不写入并且我不能使用锁,因为处理程序是时间关键的)存储在我的结构数组中的一个或多个指针。
例如
void *ptr = my_struct[2]->ptr;
最终我不关心中断处理程序读取的指针是新的还是旧的;只是它没有被破坏。显然,下面的操作不会是原子的
my_struct[index]->ptr = NULL;
但是,我能确定如果发生中断并读取 "my_struct[index]->ptr" 它不会被破坏吗?我怀疑这应该是真的,因为 (1) 指针应该完全适合寄存器(至少对于我的目标 MSP430)和 (2) 写入单个寄存器很可能是原子指令。我的推理正确吗?
在 MSP430 架构上(以及您可能使用的任何 C 编译器),指针 reads/writes 确实是原子的。因此,没有针对 MSP430 的特定 std::atomic
(或等效)支持代码。
(对于大于本机字长的类型,进行原子访问的唯一方法是禁用中断。)
在你的情况下,你只需要关心编译器可能不知道并发访问,并重新排序它自己对变量的访问。换句话说,除非您使用 volatile
访问,否则 ptr
的分配可能会被重新排序(或者在极端情况下,被优化掉)。您可以使 ptr
本身 volatile
(void * volatile ptr;
),或 add volatile when assigning it.
该芯片的 RAM 嵌入在芯片上。编译器在指针大小上与处理器一致,我想你应该没问题。
没有缓存,因此您甚至不必担心同步访问的围栏。
编辑:这里有很多其他答案,对所有答案都+1。 OP 的问题更像是一个 MSP430/gcc 编译器问题,而不是 C 语言问题,在我匆忙的情况下,在昨晚关灯睡觉之前,我给出了上述答案。我以前没有使用 MSP430 的经验,所以我上网并进行了一些探索,要求 OP 检查他们环境中的几个常量,然后直接得出结论,他们在所述情况下可能没有什么可担心的.我习惯于使用几乎不符合 K&R 标准的嵌入式 C 编译器,更不用说 C99 或 C11,但这种体验实际上早于 C11,所以我没想过要问 _Atomic
关键字是否可用(我应该有! ).所以这是另一个尝试:
如果您可以声明 _Atomic(void*) ptr;
,一定要这样做。它将确保必要的对齐并生成以原子方式写入和读取指针值的代码。正如@Lundin 指出的那样,这是 C 语言中唯一确定的原子性。在没有 _Atomic
、assert(sizeof(void*) == 2)
和 assert(&(my_struct[index]->ptr) % 2 == 0)
的情况下,后者将确保指针值存储在对齐的地址位置。 If/when 这些断言不成立,由于未对齐或指针大小超过处理器的字长,您有读取部分写入的指针值的风险。即使是这些断言也不能保证,因为它们只适用于定义了 DEBUG 的编译代码。如果您觉得需要始终验证这些约束,请改用 if(expression)
。
@CL. 关于 volatile
关键字的观点也应该牢记在心,因为编译器可以自由优化和重新排序访问,中断例程可能永远看不到真正的指针值,除非在您的代码中使用该数据之前将该数据初始化为 NULL,否则这可能会导致一些非常严重、难以诊断的错误。在没有缓存和推测执行管道的简单微处理器上,这种情况不太可能发生,但也不能排除这种情况。所以使用volatile
关键字。
TelosB使用msp430 Series 1 MCU,所有指针都是16位变量。 (在其他系列的 msp430 中,这不成立。)通常,对 msp430 上的 16 位变量的访问是原子的。但是,编译器将在多条指令中分离对指针的访问,以防有理由怀疑访问可能未对齐(来源:the TI forum):与 x86 不同,msp430 只能对对齐内存进行字访问。
在您上面提供的示例中,编译器没有理由怀疑这一点,因为指针是在没有对齐修饰符的情况下声明的结构的一部分。所以简而言之,你几乎肯定是安全的。不过,如果您要求正式保证,可能会有 none。此外,由于 CL 提到的原因,您应该使用 volatile
。
既然说的是C,贴出来的答案大部分都是废话。指针类型有多大并不重要,MPC430 是一个 16 位 MCU,就像 16 位地址总线一样。认为这在某种程度上对使 C 代码原子化很重要是天真的。
当您编写 C 语言时,任何变量(或指针)都可以存储在任何地方:寄存器中、堆栈中或完全优化掉。程序员无法控制这一点,这是一件好事。
只要你有机会将变量存储在堆栈中,你也有机会获得多条指令。给定 a = b
,那么 a
、b
或两者都可以存储在任何地方(如果有的话)。如果它们中的任何一个存储在堆栈中,则生成的机器代码很可能会导致类似以下内容:
- 指令 1:"store data from stack inside register"
- 指令 2:"do something with register".
这种情况足以破坏原子性——CPU 硬件无关紧要。即使在内核支持直接从堆栈写入其他内存的情况下,也不能保证这是在一条指令中完成的。期间.
即使您可以通过反汇编验证机器代码是原子的,这也不一定是稳定的状态。更改代码,添加更多变量,再次 link,突然之间以前是原子的代码不再是原子的,反之亦然。
C 语言中仅 现有的原子性保证是:
- C11 中的
_Atomic
关键字。
- 您手动编写所有内容的内联汇编器。
- 使用互斥锁、信号量、中断禁用等保护机制
我发现了与我遇到的问题类似的问题(即 Is changing a pointer considered an atomic action in C?),但是在编写指针时我找不到任何可以给我明确 yes/no 答案的东西.
我的问题就是这样。我有一个带有指针字段的结构指针数组。
例如
struct my_struct {
void *ptr;
};
struct my_struct *my_struct[10];
每个结构的指针字段由线程读取和写入。
例如
uint8_t index;
for(index = 0; index < 10; index++)
{
if(my_struct[index]->ptr != NULL)
{
my_struct[index]->ptr = NULL;
}
}
周期性地发生一个中断,它将读取(不写入并且我不能使用锁,因为处理程序是时间关键的)存储在我的结构数组中的一个或多个指针。
例如
void *ptr = my_struct[2]->ptr;
最终我不关心中断处理程序读取的指针是新的还是旧的;只是它没有被破坏。显然,下面的操作不会是原子的
my_struct[index]->ptr = NULL;
但是,我能确定如果发生中断并读取 "my_struct[index]->ptr" 它不会被破坏吗?我怀疑这应该是真的,因为 (1) 指针应该完全适合寄存器(至少对于我的目标 MSP430)和 (2) 写入单个寄存器很可能是原子指令。我的推理正确吗?
在 MSP430 架构上(以及您可能使用的任何 C 编译器),指针 reads/writes 确实是原子的。因此,没有针对 MSP430 的特定 std::atomic
(或等效)支持代码。
(对于大于本机字长的类型,进行原子访问的唯一方法是禁用中断。)
在你的情况下,你只需要关心编译器可能不知道并发访问,并重新排序它自己对变量的访问。换句话说,除非您使用 volatile
访问,否则 ptr
的分配可能会被重新排序(或者在极端情况下,被优化掉)。您可以使 ptr
本身 volatile
(void * volatile ptr;
),或 add volatile when assigning it.
该芯片的 RAM 嵌入在芯片上。编译器在指针大小上与处理器一致,我想你应该没问题。
没有缓存,因此您甚至不必担心同步访问的围栏。
编辑:这里有很多其他答案,对所有答案都+1。 OP 的问题更像是一个 MSP430/gcc 编译器问题,而不是 C 语言问题,在我匆忙的情况下,在昨晚关灯睡觉之前,我给出了上述答案。我以前没有使用 MSP430 的经验,所以我上网并进行了一些探索,要求 OP 检查他们环境中的几个常量,然后直接得出结论,他们在所述情况下可能没有什么可担心的.我习惯于使用几乎不符合 K&R 标准的嵌入式 C 编译器,更不用说 C99 或 C11,但这种体验实际上早于 C11,所以我没想过要问 _Atomic
关键字是否可用(我应该有! ).所以这是另一个尝试:
如果您可以声明 _Atomic(void*) ptr;
,一定要这样做。它将确保必要的对齐并生成以原子方式写入和读取指针值的代码。正如@Lundin 指出的那样,这是 C 语言中唯一确定的原子性。在没有 _Atomic
、assert(sizeof(void*) == 2)
和 assert(&(my_struct[index]->ptr) % 2 == 0)
的情况下,后者将确保指针值存储在对齐的地址位置。 If/when 这些断言不成立,由于未对齐或指针大小超过处理器的字长,您有读取部分写入的指针值的风险。即使是这些断言也不能保证,因为它们只适用于定义了 DEBUG 的编译代码。如果您觉得需要始终验证这些约束,请改用 if(expression)
。
@CL. 关于 volatile
关键字的观点也应该牢记在心,因为编译器可以自由优化和重新排序访问,中断例程可能永远看不到真正的指针值,除非在您的代码中使用该数据之前将该数据初始化为 NULL,否则这可能会导致一些非常严重、难以诊断的错误。在没有缓存和推测执行管道的简单微处理器上,这种情况不太可能发生,但也不能排除这种情况。所以使用volatile
关键字。
TelosB使用msp430 Series 1 MCU,所有指针都是16位变量。 (在其他系列的 msp430 中,这不成立。)通常,对 msp430 上的 16 位变量的访问是原子的。但是,编译器将在多条指令中分离对指针的访问,以防有理由怀疑访问可能未对齐(来源:the TI forum):与 x86 不同,msp430 只能对对齐内存进行字访问。
在您上面提供的示例中,编译器没有理由怀疑这一点,因为指针是在没有对齐修饰符的情况下声明的结构的一部分。所以简而言之,你几乎肯定是安全的。不过,如果您要求正式保证,可能会有 none。此外,由于 CL 提到的原因,您应该使用 volatile
。
既然说的是C,贴出来的答案大部分都是废话。指针类型有多大并不重要,MPC430 是一个 16 位 MCU,就像 16 位地址总线一样。认为这在某种程度上对使 C 代码原子化很重要是天真的。
当您编写 C 语言时,任何变量(或指针)都可以存储在任何地方:寄存器中、堆栈中或完全优化掉。程序员无法控制这一点,这是一件好事。
只要你有机会将变量存储在堆栈中,你也有机会获得多条指令。给定 a = b
,那么 a
、b
或两者都可以存储在任何地方(如果有的话)。如果它们中的任何一个存储在堆栈中,则生成的机器代码很可能会导致类似以下内容:
- 指令 1:"store data from stack inside register"
- 指令 2:"do something with register".
这种情况足以破坏原子性——CPU 硬件无关紧要。即使在内核支持直接从堆栈写入其他内存的情况下,也不能保证这是在一条指令中完成的。期间.
即使您可以通过反汇编验证机器代码是原子的,这也不一定是稳定的状态。更改代码,添加更多变量,再次 link,突然之间以前是原子的代码不再是原子的,反之亦然。
C 语言中仅 现有的原子性保证是:
- C11 中的
_Atomic
关键字。 - 您手动编写所有内容的内联汇编器。
- 使用互斥锁、信号量、中断禁用等保护机制