分配一个无符号大小的连续内存结构是一个原子操作吗?
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*)®->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;
}
我应该在寄存器中写入多个字段。供应商“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*)®->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;
}