In a dual-core embedded system, core0 needs to interrupt core1 for several reasons. An enum paired with a union is used to pass both the reason and the data of the interrupt. The code is written in C.
#include <stdatomic.h>
typedef enum { // the interrupt reason, also indicates which member is valid in the union
kData0,
// ...
} InterruptId;
typedef struct {
_Atomic InterruptId interruptId;
union { // data associated with interruptId
int data0;
// ...
};
} SharedMemory;
// core0
void InterruptCore1(); // write some registers in ARM GIC to interrupt core1
void PassData0ToCore1(SharedMemory *mem, int data0) {
mem->data0 = data0;
atomic_store_explicit(&mem->interruptId, kData0, memory_order_release);
InterruptCore1();
}
// core1
void ProcessData0(int data0);
void OnCore0Interrupt(SharedMemory *mem) {
InterruptId id =
atomic_load_explicit(&mem->interruptId, memory_order_acquire);
switch (id) {
case kData0:
ProcessData0(mem->data0);
break;
}
}
The above code seems to be incorrect, as core1 may see an outdated value of interruptId
. As I understand it, the Release-Acquire ordering doesn’t guarantee that load can read the most recent value. Maybe atomic_thread_fence is the solution here.
typedef struct {
InterruptId interruptId;
union {
int data0;
// ...
};
} SharedMemory;
// core0
void InterruptCore1(); // write some registers in ARM GIC to interrupt core1
void PassData0ToCore1(SharedMemory *mem, int data0) {
mem->data0 = data0;
mem->interruptId = kData0;
atomic_thread_fence(memory_order_release);
InterruptCore1();
}
// core1
void ProcessData0(int data0);
void OnCore0Interrupt(SharedMemory *mem) {
atomic_thread_fence(memory_order_acquire);
InterruptId id = mem->interruptId;
switch (id) {
case kData0:
ProcessData0(mem->data0);
break;
}
}
Both the release and acquire version of atomic_thread_fence
compile to dmb ish
, which seems to be correct in the machine level. But then I come to read the C++ version of atomic_thread_fence
and find it also requires the use of atomic variables, and the synchronization only works if load actually reads the released value.
Does that mean the second solution has the same problem as the first (core1 may read an outdated value)? How to guarantee core1 can read the most recent value? Thanks very much.
1
As I understand it, the Release-Acquire ordering doesn’t guarantee that load can read the most recent value.
All memory orders guarantee that all threads see “the most recent” write to all atomic objects. The differences arise from what state you can see in other objects, i.e. whether writes to other objects can be re-ordered around the atomic reads or writes.