Architecture · 15 pages
Memory model & barriers
How loads and stores are ordered on ARM — relaxed by default, with explicit barriers (DMB, DSB, ISB) where you need them. Plus the Cortex-M memory map.
ARM is a weakly ordered architecture. The CPU and the bus are free to reorder loads and stores as long as single-threaded program semantics are preserved. Most of the time you don't notice. The moment you start touching memory-mapped peripherals or doing lock-free inter-core communication, you notice hard.
The Cortex-M address space
Every Cortex-M divides its 4 GB address space into eight 512 MB regions with fixed default attributes:
The regions default to specific memory types (Normal, Device, Strongly-ordered) which dictate what reordering and caching the bus is allowed to do. An MPU can override these.
| Type | Reordered? | Cacheable? | Example region |
|---|---|---|---|
| Normal | yes | yes | Flash, SRAM |
| Device | no | no | Peripherals |
| Strongly-ordered | no | no | PPB, SO regions |
The three barriers
ARM has exactly three memory barriers, and the names tell you what they do.
DMB — Data Memory Barrier
All explicit memory accesses before the DMB complete (from the observer's point of view) before any explicit memory accesses after the DMB are observed. It's the cheapest barrier — it doesn't stall the pipeline, it just constrains the bus.
Use DMB when:
- Two cores or a core + DMA are sharing a buffer.
- You're implementing a software mutex or producer/consumer queue.
- You hand a pointer to a peripheral that will then read the memory it points to.
// Producer
buffer[head] = item; // store data
__DMB(); // ensure data is visible before head update
head_index = (head + 1); // store updated index — consumer can now see itemDSB — Data Synchronisation Barrier
A DMB plus the processor stalls until the prior accesses have completed. Stronger and more expensive.
Use DSB when:
- You wrote to a peripheral register and the next instruction depends on the write having taken effect (e.g. enabling an interrupt and immediately executing
WFI, disabling an interrupt and immediately returning from a handler). - You need to flush the write buffer before sleeping.
NVIC_DisableIRQ(SOME_IRQ);
__DSB(); // make sure the disable is observed
__ISB(); // and that pending instructions are flushed
// safe to assume IRQ won't fire from hereISB — Instruction Synchronisation Barrier
Flushes the instruction pipeline. The processor refetches all instructions after the ISB. Cheap from a bus perspective but expensive in pipeline terms (~5–10 cycles).
Use ISB when:
- You just modified a system register that changes how subsequent instructions execute (e.g.
CONTROL,VTOR, MPU region settings,FAULTMASK). - You self-modified code or remapped memory.
SCB->VTOR = new_table_address;
__DSB(); // make sure the write to VTOR completes
__ISB(); // flush prefetch — next IRQ uses new vector tablePutting it together
The classic three-barrier dance after enabling a peripheral clock or modifying the MPU:
MPU->RNR = region;
MPU->RBAR = base;
MPU->RASR = attr | MPU_RASR_ENABLE_Msk;
__DSB(); // make sure the MPU writes have completed
__ISB(); // flush any prefetched instructions executed with old configAtomics on Cortex-M
- Cortex-M0/M0+/M23: no exclusive load/store. The standard pattern is to disable interrupts (
__disable_irq()or a critical section macro) around the read-modify-write. - Cortex-M3 and up: have
LDREX/STREXexclusive monitors. C11<stdatomic.h>and C++11<atomic>will use them when targeting ARMv7-M or later. - Multi-master systems (e.g. M0+ core + a DMA controller): the exclusive monitor only covers the CPU. You still need software protocols (or a vendor-provided hardware semaphore — STM32 HSEM, NXP SEMA42) to coordinate.
See also
- Exception model — barriers in interrupt entry/exit.
- NVIC deep dive — when you need DSB+ISB and when CMSIS does it for you.
- DMA — the most common source of "I forgot a barrier" bugs.