原子地覆盖指针

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 语言中唯一确定的原子性。在没有 _Atomicassert(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,那么 ab 或两者都可以存储在任何地方(如果有的话)。如果它们中的任何一个存储在堆栈中,则生成的机器代码很可能会导致类似以下内容:

  • 指令 1:"store data from stack inside register"
  • 指令 2:"do something with register".

这种情况足以破坏原子性——CPU 硬件无关紧要。即使在内核支持直接从堆栈写入其他内存的情况下,也不能保证这是在一条指令中完成的。期间.

即使您可以通过反汇编验证机器代码是原子的,这也不一定是稳定的状态。更改代码,添加更多变量,再次 link,突然之间以前是原子的代码不再是原子的,反之亦然。

C 语言中 现有的原子性保证是:

  • C11 中的 _Atomic 关键字。
  • 您手动编写所有内容的内联汇编器。
  • 使用互斥锁、信号量、中断禁用等保护机制