分配一个无符号大小的连续内存结构是一个原子操作吗?

Is assigning an unsigned size, contingous memory struct an atomic operation?

我应该在寄存器中写入多个字段。供应商“API”是一个映射到内存地址的结构。该结构看起来像这样:

typedef struct
{
    unsigned Reg_A : 28;
    unsigned Reg_B : 1;
    unsigned Reg_C : 3;
}RegisterFooFields;

typedef union
{
    unsigned U;
    RegisterFooFields B;
}RegisterFoo;

我应该原子地写所有的 A、B 和 C,因为一个约束另一个。如果我分配给 RegisterFoo.B,C 是否保证会发生这种情况?

RegsiterFooFields fields = {.A = 123, .B = 0, .C = 0b10};
RegisterFooAddress->B = fields; /* Is this an atomic write (mov) operation? */

我的环境是 C99,32 位。


编辑:

RegisterFooAddress 被声明为可变的。


编辑 2:

我担心的是编译器为每个字段生成一系列分配,适当屏蔽,导致瞬态寄存器状态,其中(例如)Reg_A 已更新但 Reg_B并且 Reg_C 没有,导致硬件可能读取瞬态。

在积极优化的情况下读取生成的程序集是不可行的,可能内联代码。

Does C guarantee that to happen if I assign to RegisterFoo.B?

不,不是。没有这样的保证。

但在您的平台上,您的编译器可能会将其编译为一条指令,使其成为“原子指令”。检查你的编译器文档,检查生成的程序集。

可以肯定的是,我会编写具有 volatile 访问权限的代码。我通常希望 volatile 访问是“原子的”,因为如果体系结构支持它们被编译为单个 mov 指令,例如:

void RegisterFoo_assign_RegisterFooFields(RegisterFoo *reg, RegisterFooFields fields) {
      static_assert(sizeof(RegisterFooFields) == 4, "");
      static_assert(sizeof(RegisterFoo) == 4, "");
      static_assert(sizeof(unsigned) == 4, "");
      static_assert(_Alignof(unsigned) == _Alignof(RegisterFoo), "");
      unsigned var;
      memcpy(&var, &fields, 4);
      *(volatile unsigned*)reg = var;
      // or really just:
      *(volatile unsigned*)&reg->U = var;
}

gcc 生成的程序集looks nice on godbolt

如果我们考虑映射到地址的硬件寄存器 space 并且硬件规范不保证访问的强顺序,则即使处理器使用以下指令,C 语言中也没有机制来保证一致性原子的。

所以原子性不保证这个函数的return值会和它的参数

一样
volatile uint32_t *reg = (void *)0xE0000454; // some address of the hardware register

uint32_t writeAndRead(uint32_t val)
{
    *reg = val;
    return *reg;
}

在这种情况下,必须使用一些障碍。有时使用核心指令就足够了,有时需要更复杂的解决方案。

static inline __attribute__((always_inline)) void __DSB(void)
{
  asm volatile ("dsb 0xF":::"memory");
}

uint32_t writeAndRead1(uint32_t val)
{
    *reg = val;
    __DSB();
    return *reg;
}