Embedded

Memory barriers in embedded

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: Memory barriers (DMB, DSB, ISB on Arm) force an ordering guarantee that neither the compiler nor the CPU's out-of-order pipeline will otherwise give you — use them whenever the correctness of your code depends on one memory access actually completing, and being observed, before another.

    Every embedded engineer eventually hits a bug that cannot be reproduced under the debugger, vanishes when optimisation is turned down, and reappears the moment real traffic hits the device. A large share of these are ordering bugs: the program wrote the code in one order, but the hardware executed or observed it in another. Memory barriers are the tool that closes that gap. This article explains the two independent reordering sources, the three Arm barrier instructions, and the concrete situations where you genuinely need them.

    Two reorderings, not one

    There are two distinct actors that can rearrange your memory accesses, and they are easy to confuse:

    • The compiler reorders at build time. If it cannot see a dependency, it will freely move loads and stores, cache values in registers, or delete "redundant" accesses. This is what volatile and compiler memory clobbers address — see our deep dive on the volatile keyword in embedded C.
    • The CPU reorders at run time. A Cortex-M7, Cortex-A53 or any modern core issues loads and stores speculatively, buffers writes, and allows accesses to complete out of program order. volatile does nothing here — it is purely a compiler construct.

    Key fact: volatile stops the compiler from reordering, but it emits no barrier instruction. On a Cortex-A class core with a write buffer, two volatile stores can still reach memory in the opposite order. You need a real barrier for that.

    Program order vs observed order Program order (source) STR data STR flag = 1 no barrier Seen by other core / DMA flag = 1 seen data not yet visible Reader acts on the flag before the data lands, so it reads stale data. Fix: a DMB between the two stores. CourseTron · coursetron.com
    Without a barrier, a consumer can see the ready-flag before the payload is visible.

    The three Arm barriers

    InstructionGuaranteesTypical use
    DMB (Data Memory Barrier)All memory accesses before it are observed before any after it. It does not wait for them to complete.Ordering two data accesses, e.g. payload then flag.
    DSB (Data Synchronization Barrier)Stronger: execution stalls until all prior memory accesses have actually completed.Before enabling an MPU region, after a cache maintenance op, before WFI.
    ISB (Instruction Synchronization Barrier)Flushes the pipeline so instructions after it are re-fetched; context-changing effects take hold.After writing a system control register (e.g. SCB, VTOR) or self-modifying code.

    A useful mental model: DMB orders the appearance of data, DSB orders the completion of data, and ISB orders the fetching of instructions. The CMSIS intrinsics __DMB(), __DSB() and __ISB() are the portable way to emit them from C without inline assembly.

    A DMB holds later accesses back DMB Before the barrier After the barrier STR LDR The later access passes only after the barrier. CourseTron · coursetron.com
    Animated: the second access is held at the barrier until prior accesses are ordered.

    When you actually need a barrier

    • Lock-free producer/consumer handoff. Write the buffer, DMB, then set the ready flag. The reader checks the flag, DMB, then reads the buffer. Skipping either barrier lets the flag overtake the data.
    • Memory-mapped peripheral sequencing. Clearing an interrupt in a peripheral then returning from the ISR: issue DSB so the clear reaches the device before the WFI or the pipeline re-enables interrupts — otherwise a spurious re-entry fires.
    • System register changes. After relocating the vector table via SCB->VTOR, or changing the MPU/MMU configuration, an ISB is required for the change to take effect before subsequent instructions.
    • Cache + DMA coherency. On a Cortex-M7 with D-cache, clean the cache (DSB) before starting a DMA read, and invalidate before a CPU read of DMA-written data.

    Key fact: On single-core Cortex-M0/M0+/M3 with the default memory map, Device and Strongly-Ordered regions are already ordered by the architecture, so peripheral register sequences often work without DMB. The barrier becomes mandatory on cores with write buffers, caches, or multiple observers (A-class, M7, SMP).

    Barriers vs atomics vs volatile

    These three solve overlapping but different problems. volatile forces the compiler to actually perform each access. Atomics (C11 ) give you both compiler and CPU ordering through acquire/release semantics, compiling down to the right DMB automatically. Raw barriers are the lowest level and are what you reach for in driver code, RTOS context switches, and bare-metal MMIO where you want explicit control. Static analysis flows from Synopsys Coverity or Siemens Polyspace can flag some missing-ordering patterns, but ordering correctness ultimately needs human reasoning about the hardware model.

    A worked handoff

    Producer: sensor_buf[i] = sample; then __DMB(); then data_ready = 1;. Consumer: if (data_ready) { __DMB(); process(sensor_buf); }. The two __DMB() calls form a release/acquire pair. Remove the producer's barrier and, on an M7, the write buffer can flush data_ready first; the consumer then reads a half-written sample. The bug surfaces maybe once per million iterations — exactly the kind of field-only failure that costs weeks. If you are building production firmware and want this reviewed by engineers who do it daily, Avecas design services runs embedded and SoC validation flows end to end.

    Producer / consumer handoff Producer Consumer sensor_buf[i] = sample __DMB() (release) data_ready = 1 if (data_ready) __DMB() (acquire) process(sensor_buf) Flag seen by consumer implies the data is visible too. Remove the producer barrier and the flag can overtake the data. CourseTron · coursetron.com
    The release/acquire pair from the worked handoff: one DMB on each side.

    Want to master concurrency, caches and bare-metal driver design hands-on? Explore the CourseTron Embedded Systems track — with lab exercises built around real Cortex-M and Cortex-A targets.

    FAQ

    Is volatile enough for multithreaded flags?

    No. volatile only controls the compiler. On any core that reorders memory or buffers writes you must add a barrier or use C11 atomics; otherwise the flag and its payload can be observed out of order.

    What is the difference between DMB and DSB?

    DMB only guarantees the observed order of memory accesses and lets the pipeline continue. DSB actually stalls the core until prior accesses complete, which is what you need before cache operations, MPU changes, or low-power entry.

    Do I need ISB after every system register write?

    Only when a subsequent instruction depends on the new state — vector table relocation, MPU/MMU reconfiguration, or enabling the FPU. For most peripheral register writes an ISB is unnecessary.

    Can I just use C11 atomics instead of raw barriers?

    Often yes. Acquire/release atomics compile to the correct barriers and are more portable. Raw __DMB()/__DSB()/__ISB() remain preferable for MMIO, cache maintenance, and hand-tuned RTOS code where you want explicit control.

    For a related compiler-side companion to this topic, also read cache coherency and DMA on the Cortex-M7.

    Advertisement